diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..538d79a
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,2 @@
+@rkheftan:registry=https://npm.pkg.github.com
+//npm.pkg.github.com/:_authToken=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
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 8699e54..895fb99 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,13 +8,18 @@
"name": "harmony-club",
"version": "0.0.0",
"dependencies": {
+ "@date-io/dayjs": "^3.2.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
- "@mui/material": "^7.2.0",
+ "@mui/material": "^7.3.1",
"@mui/stylis-plugin-rtl": "^7.2.0",
- "@rkheftan/harmony-ui": "^0.1.6",
- "@types/stylis": "^4.2.7",
+ "@mui/x-data-grid-premium": "^8.10.0",
+ "@mui/x-date-pickers": "^8.10.0",
+ "@rkheftan/harmony-ui": "^0.1.8",
"axios": "^1.11.0",
+ "date-fns": "^4.1.0",
+ "date-fns-jalali": "^4.0.0-0",
+ "dayjs": "^1.11.13",
"i18next": "^25.3.0",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
@@ -31,8 +36,8 @@
"devDependencies": {
"@eslint/js": "^9.29.0",
"@types/node": "^24.0.10",
- "@types/react": "^19.1.8",
- "@types/react-dom": "^19.1.6",
+ "@types/react": "^19.1.10",
+ "@types/react-dom": "^19.1.7",
"@types/stylis": "^4.2.7",
"@typescript-eslint/eslint-plugin": "^8.35.1",
"@typescript-eslint/parser": "^8.35.1",
@@ -88,22 +93,22 @@
}
},
"node_modules/@babel/core": {
- "version": "7.27.7",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz",
- "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==",
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz",
+ "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.27.5",
+ "@babel/generator": "^7.28.3",
"@babel/helper-compilation-targets": "^7.27.2",
- "@babel/helper-module-transforms": "^7.27.3",
- "@babel/helpers": "^7.27.6",
- "@babel/parser": "^7.27.7",
+ "@babel/helper-module-transforms": "^7.28.3",
+ "@babel/helpers": "^7.28.3",
+ "@babel/parser": "^7.28.3",
"@babel/template": "^7.27.2",
- "@babel/traverse": "^7.27.7",
- "@babel/types": "^7.27.7",
+ "@babel/traverse": "^7.28.3",
+ "@babel/types": "^7.28.2",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -119,15 +124,15 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.27.5",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz",
- "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==",
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
+ "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.27.5",
- "@babel/types": "^7.27.3",
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.25",
+ "@babel/parser": "^7.28.3",
+ "@babel/types": "^7.28.2",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
},
"engines": {
@@ -165,15 +170,15 @@
}
},
"node_modules/@babel/helper-module-transforms": {
- "version": "7.27.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
- "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
+ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1",
- "@babel/traverse": "^7.27.3"
+ "@babel/traverse": "^7.28.3"
},
"engines": {
"node": ">=6.9.0"
@@ -221,9 +226,9 @@
}
},
"node_modules/@babel/helpers": {
- "version": "7.27.6",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz",
- "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz",
+ "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -235,12 +240,12 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.27.7",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz",
- "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==",
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz",
+ "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.27.7"
+ "@babel/types": "^7.28.2"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -305,18 +310,18 @@
}
},
"node_modules/@babel/traverse": {
- "version": "7.27.7",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz",
- "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==",
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz",
+ "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.27.5",
- "@babel/parser": "^7.27.7",
+ "@babel/generator": "^7.28.3",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.3",
"@babel/template": "^7.27.2",
- "@babel/types": "^7.27.7",
- "debug": "^4.3.1",
- "globals": "^11.1.0"
+ "@babel/types": "^7.28.2",
+ "debug": "^4.3.1"
},
"engines": {
"node": ">=6.9.0"
@@ -344,6 +349,29 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@date-io/core": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@date-io/core/-/core-3.2.0.tgz",
+ "integrity": "sha512-hqwXvY8/YBsT9RwQITG868ZNb1MVFFkF7W1Ecv4P472j/ZWa7EFcgSmxy8PUElNVZfvhdvfv+a8j6NWJqOX5mA==",
+ "license": "MIT"
+ },
+ "node_modules/@date-io/dayjs": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@date-io/dayjs/-/dayjs-3.2.0.tgz",
+ "integrity": "sha512-+3LV+3N+cpQbEtmrFo8odg07k02AFY7diHgbi2EKYYANOOCPkDYUjDr2ENiHuYNidTs3tZwzDKckZoVNN4NXxg==",
+ "license": "MIT",
+ "dependencies": {
+ "@date-io/core": "^3.2.0"
+ },
+ "peerDependencies": {
+ "dayjs": "^1.8.17"
+ },
+ "peerDependenciesMeta": {
+ "dayjs": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@emotion/babel-plugin": {
"version": "11.13.5",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
@@ -508,10 +536,78 @@
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
"license": "MIT"
},
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz",
+ "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz",
+ "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz",
+ "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz",
+ "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
- "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz",
+ "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==",
"cpu": [
"arm64"
],
@@ -525,6 +621,363 @@
"node": ">=18"
}
},
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz",
+ "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz",
+ "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz",
+ "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz",
+ "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz",
+ "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz",
+ "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz",
+ "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz",
+ "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz",
+ "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz",
+ "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz",
+ "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz",
+ "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz",
+ "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz",
+ "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz",
+ "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz",
+ "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz",
+ "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz",
+ "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz",
+ "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz",
+ "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz",
+ "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
@@ -697,7 +1150,6 @@
"resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz",
"integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/node": "^14.0.1",
"lodash.escaperegexp": "^4.1.2",
@@ -711,15 +1163,13 @@
"version": "14.18.63",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz",
"integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/@fast-csv/parse": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz",
"integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/node": "^14.0.1",
"lodash.escaperegexp": "^4.1.2",
@@ -734,15 +1184,13 @@
"version": "14.18.63",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz",
"integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/@fingerprintjs/fingerprintjs": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/@fingerprintjs/fingerprintjs/-/fingerprintjs-3.4.2.tgz",
"integrity": "sha512-3Ncze6JsJpB7BpYhqIgvBpfvEX1jsEKrad5hQBpyRQxtoAp6hx3+R46zqfsuQG4D9egQZ+xftQ0u4LPFMB7Wmg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"tslib": "^2.4.1"
}
@@ -814,9 +1262,9 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.11",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.11.tgz",
- "integrity": "sha512-C512c1ytBTio4MrpWKlJpyFHT6+qfFL8SZ58zBzJ1OOzUEjHeF1BtjY2fH7n4x/g2OV/KiiMLAivOp1DXmiMMw==",
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -833,15 +1281,15 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.3",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.3.tgz",
- "integrity": "sha512-AiR5uKpFxP3PjO4R19kQGIMwxyRyPuXmKEEy301V1C0+1rVjS94EZQXf1QKZYN8Q0YM+estSPhmx5JwNftv6nw==",
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.28",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.28.tgz",
- "integrity": "sha512-KNNHHwW3EIp4EDYOvYFGyIFfx36R2dNJYH4knnZlF8T5jdbD5Wx8xmSaQ2gP9URkJ04LGEtlcCtwArKcmFcwKw==",
+ "version": "0.3.30",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz",
+ "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -1076,16 +1524,15 @@
}
},
"node_modules/@mui/x-data-grid": {
- "version": "8.10.0",
- "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-8.10.0.tgz",
- "integrity": "sha512-NMOZyDcE9vqn0qEv0z6DqkXwzIOj4ZFy4QC0RcUjEvBmjwdRc3KCh9XSWAuqmpc23B4M9cydVVkt0CBfOJKwsQ==",
+ "version": "8.10.1",
+ "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-8.10.1.tgz",
+ "integrity": "sha512-IahRHuI5OdF5d6wkPaURoi3viVQ2tESczKPC3gztLL0bQ1POG9SIg7Xtx+x7z1Ya4UHXF75gkytFOHEOyp/FDg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.28.2",
- "@mui/utils": "^7.2.0",
+ "@mui/utils": "^7.3.1",
"@mui/x-internals": "8.10.0",
- "@mui/x-virtualizer": "0.1.1",
+ "@mui/x-virtualizer": "0.1.2",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
"use-sync-external-store": "^1.5.0"
@@ -1115,18 +1562,17 @@
}
},
"node_modules/@mui/x-data-grid-premium": {
- "version": "8.10.0",
- "resolved": "https://registry.npmjs.org/@mui/x-data-grid-premium/-/x-data-grid-premium-8.10.0.tgz",
- "integrity": "sha512-oNDmzDNoaxVNDSG+vY88okGoVF6hhJ4acpQlb+SYC63hQUnZlB6nQpgVGsDFF6V7A8lq2lDLTnTEZnWlMjuZZw==",
+ "version": "8.10.1",
+ "resolved": "https://registry.npmjs.org/@mui/x-data-grid-premium/-/x-data-grid-premium-8.10.1.tgz",
+ "integrity": "sha512-DPBuTqSDAwkwT9rcsZTzd22cHyTkY2s1eQZl442o49vqcKuKfer+9yi270bCs8RVEsfbTGfnEzyVabaN4ZKTFw==",
"license": "SEE LICENSE IN LICENSE",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.28.2",
- "@mui/utils": "^7.2.0",
- "@mui/x-data-grid": "8.10.0",
- "@mui/x-data-grid-pro": "8.10.0",
+ "@mui/utils": "^7.3.1",
+ "@mui/x-data-grid": "8.10.1",
+ "@mui/x-data-grid-pro": "8.10.1",
"@mui/x-internals": "8.10.0",
- "@mui/x-license": "8.10.0",
+ "@mui/x-license": "8.10.1",
"@types/format-util": "^1.0.4",
"clsx": "^2.1.1",
"exceljs": "^4.4.0",
@@ -1153,17 +1599,16 @@
}
},
"node_modules/@mui/x-data-grid-pro": {
- "version": "8.10.0",
- "resolved": "https://registry.npmjs.org/@mui/x-data-grid-pro/-/x-data-grid-pro-8.10.0.tgz",
- "integrity": "sha512-jg5WZakq8QVnYgF1KQ6EFWqtjPXl5Aww4o9bJQOiq1I5IGXqQJdVm9VGdDK0Xywn+FdNiU4VbdQhS++B601b5w==",
+ "version": "8.10.1",
+ "resolved": "https://registry.npmjs.org/@mui/x-data-grid-pro/-/x-data-grid-pro-8.10.1.tgz",
+ "integrity": "sha512-S72ROOm2hPrQYsKiWraDbpCXjug1cPvhaLbr3z0ZNPw5wfE7RLr9OJGfW28PlfIaosSud5nBGxwfxj32+aZgZA==",
"license": "SEE LICENSE IN LICENSE",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.28.2",
- "@mui/utils": "^7.2.0",
- "@mui/x-data-grid": "8.10.0",
+ "@mui/utils": "^7.3.1",
+ "@mui/x-data-grid": "8.10.1",
"@mui/x-internals": "8.10.0",
- "@mui/x-license": "8.10.0",
+ "@mui/x-license": "8.10.1",
"@types/format-util": "^1.0.4",
"clsx": "^2.1.1",
"prop-types": "^15.8.1"
@@ -1188,12 +1633,77 @@
}
}
},
+ "node_modules/@mui/x-date-pickers": {
+ "version": "8.10.0",
+ "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-8.10.0.tgz",
+ "integrity": "sha512-3nY+SS2/JtqcptQodECIyWKsTvPBDAcXKkyW65R4rQUCrnV6tuzriSrzy/FEYqTK0hyXYPIGJhQ6A0FbtQ9AkQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.2",
+ "@mui/utils": "^7.2.0",
+ "@mui/x-internals": "8.10.0",
+ "@types/react-transition-group": "^4.4.12",
+ "clsx": "^2.1.1",
+ "prop-types": "^15.8.1",
+ "react-transition-group": "^4.4.5"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.9.0",
+ "@emotion/styled": "^11.8.1",
+ "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0",
+ "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
+ "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0",
+ "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0",
+ "dayjs": "^1.10.7",
+ "luxon": "^3.0.2",
+ "moment": "^2.29.4",
+ "moment-hijri": "^2.1.2 || ^3.0.0",
+ "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ },
+ "date-fns": {
+ "optional": true
+ },
+ "date-fns-jalali": {
+ "optional": true
+ },
+ "dayjs": {
+ "optional": true
+ },
+ "luxon": {
+ "optional": true
+ },
+ "moment": {
+ "optional": true
+ },
+ "moment-hijri": {
+ "optional": true
+ },
+ "moment-jalaali": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@mui/x-internals": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.10.0.tgz",
"integrity": "sha512-stYhWBeCKfV2/ltAWShZ3ZJ51otbqpMpC+krWWoIsxM8TuvGzwXw5YMU9L2fTb8hRstsiOCQfEzIn12Ii7+N0Q==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.28.2",
"@mui/utils": "^7.2.0",
@@ -1212,16 +1722,15 @@
}
},
"node_modules/@mui/x-license": {
- "version": "8.10.0",
- "resolved": "https://registry.npmjs.org/@mui/x-license/-/x-license-8.10.0.tgz",
- "integrity": "sha512-N6grkf44ESMmQp8bqSNKmWLIsf7IfsfJUr2PKDH07PVfJHTwvyQpUKBF+mLUtcd/GNDyUhqyDK98zx9AwGzSwA==",
+ "version": "8.10.1",
+ "resolved": "https://registry.npmjs.org/@mui/x-license/-/x-license-8.10.1.tgz",
+ "integrity": "sha512-v0Rusp/L0deizvEf2GapfTWJDI9DKVVAGNf7zTMdbwrfl/iY9aL9UxLaxvv86P8AfkDGWUfh7I9sOOzwZvZNng==",
"license": "SEE LICENSE IN LICENSE",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.28.2",
- "@mui/utils": "^7.2.0",
+ "@mui/utils": "^7.3.1",
"@mui/x-internals": "8.10.0",
- "@mui/x-telemetry": "8.5.3"
+ "@mui/x-telemetry": "8.5.4"
},
"engines": {
"node": ">=14.0.0"
@@ -1231,16 +1740,15 @@
}
},
"node_modules/@mui/x-telemetry": {
- "version": "8.5.3",
- "resolved": "https://registry.npmjs.org/@mui/x-telemetry/-/x-telemetry-8.5.3.tgz",
- "integrity": "sha512-vBLVBXCBWY44HonjRefpYjowEXa25k2AtAXkWk2tHfL3/unnnexrYPosuo/p4giIWer0pMy/bPqGY2qM0xlM+g==",
+ "version": "8.5.4",
+ "resolved": "https://registry.npmjs.org/@mui/x-telemetry/-/x-telemetry-8.5.4.tgz",
+ "integrity": "sha512-SjzC0i0Fi3lJ2BWjqyNPDbx5zdlrexYB1VhSerZ7OkS3ilh+0wXIA5C1p0E+1jK5+OVXqs6ICebdjNXZDfcE6A==",
"hasInstallScript": true,
"license": "SEE LICENSE IN LICENSE",
- "peer": true,
"dependencies": {
- "@babel/runtime": "^7.27.6",
+ "@babel/runtime": "^7.28.2",
"@fingerprintjs/fingerprintjs": "^3.4.2",
- "ci-info": "^4.2.0",
+ "ci-info": "^4.3.0",
"conf": "^11.0.2",
"is-docker": "^3.0.0",
"node-machine-id": "^1.1.12"
@@ -1250,14 +1758,13 @@
}
},
"node_modules/@mui/x-virtualizer": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/@mui/x-virtualizer/-/x-virtualizer-0.1.1.tgz",
- "integrity": "sha512-pZ84wPu/97Z6g2HF7D4t8X5GSgc+Gr3EoJJpGv1SP3mAX2OcZtYhXiUyQzvHPm2jvDQuxIIzwXT3hMIEgdDPPQ==",
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/@mui/x-virtualizer/-/x-virtualizer-0.1.2.tgz",
+ "integrity": "sha512-RCvOy/gpeGr4tpQm3WkK9bafy8HMtJima4kvLWWijhqwhjgFd74IsXIJuxg9SA6oGHcpKrwkv2JgIhSraENCXw==",
"license": "MIT",
- "peer": true,
"dependencies": {
- "@babel/runtime": "^7.27.4",
- "@mui/utils": "^7.2.0",
+ "@babel/runtime": "^7.28.2",
+ "@mui/utils": "^7.3.1",
"@mui/x-internals": "8.10.0"
},
"engines": {
@@ -1334,18 +1841,18 @@
}
},
"node_modules/@rkheftan/harmony-ui": {
- "version": "0.1.6",
- "resolved": "https://npm.pkg.github.com/download/@rkheftan/harmony-ui/0.1.6/6a5193594a84e443cec9d657a45df92bf0ba4a19",
- "integrity": "sha512-1Dp2YGSZ4J02quJ5R10DBzmuL8rDQBwbr0++0AJXBs00gcBF537edy9eG5du6p9fMxWZra057dXYLauHgMFRZA==",
+ "version": "0.1.8",
+ "resolved": "https://npm.pkg.github.com/download/@rkheftan/harmony-ui/0.1.8/ceb42e35ecc6f5ec4ab312dba32c73d8175ab861",
+ "integrity": "sha512-taau98lOVAqd8Oc4SuP8l0Yo6qTTLH24HPWIs/MLAcaKQrVR0et5gZ+PdQswoUko6/019fe+4Wx9871AiulLLg==",
"peerDependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
- "@mui/material": "^7.2.0",
- "@mui/x-data-grid-premium": "^8.8.0",
+ "@mui/material": "^7.3.1",
+ "@mui/x-data-grid-premium": "^8.10.0",
"iconsax-react": "^0.0.8",
"react": "^19.1.0",
"react-dom": "^19.1.0",
- "react-router-dom": "^7.7.1"
+ "react-router-dom": "^7.8.0"
}
},
"node_modules/@rolldown/pluginutils": {
@@ -1425,8 +1932,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@types/format-util/-/format-util-1.0.4.tgz",
"integrity": "sha512-xrCYOdHh5zA3LUrn6CvspYwlzSWxPso11Lx32WnAG6KvLCRecKZ/Rh21PLXUkzUFsQmrGcx/traJAFjR6dVS5Q==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
@@ -1436,9 +1942,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "24.0.10",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz",
- "integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==",
+ "version": "24.3.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
+ "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1458,9 +1964,9 @@
"license": "MIT"
},
"node_modules/@types/react": {
- "version": "19.1.8",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz",
- "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
+ "version": "19.1.10",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.10.tgz",
+ "integrity": "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==",
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
@@ -1493,17 +1999,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.35.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.1.tgz",
- "integrity": "sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg==",
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.1.tgz",
+ "integrity": "sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.35.1",
- "@typescript-eslint/type-utils": "8.35.1",
- "@typescript-eslint/utils": "8.35.1",
- "@typescript-eslint/visitor-keys": "8.35.1",
+ "@typescript-eslint/scope-manager": "8.39.1",
+ "@typescript-eslint/type-utils": "8.39.1",
+ "@typescript-eslint/utils": "8.39.1",
+ "@typescript-eslint/visitor-keys": "8.39.1",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -1517,7 +2023,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.35.1",
+ "@typescript-eslint/parser": "^8.39.1",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
@@ -1533,16 +2039,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.35.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.1.tgz",
- "integrity": "sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==",
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.1.tgz",
+ "integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.35.1",
- "@typescript-eslint/types": "8.35.1",
- "@typescript-eslint/typescript-estree": "8.35.1",
- "@typescript-eslint/visitor-keys": "8.35.1",
+ "@typescript-eslint/scope-manager": "8.39.1",
+ "@typescript-eslint/types": "8.39.1",
+ "@typescript-eslint/typescript-estree": "8.39.1",
+ "@typescript-eslint/visitor-keys": "8.39.1",
"debug": "^4.3.4"
},
"engines": {
@@ -1558,14 +2064,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.35.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.1.tgz",
- "integrity": "sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==",
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.1.tgz",
+ "integrity": "sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.35.1",
- "@typescript-eslint/types": "^8.35.1",
+ "@typescript-eslint/tsconfig-utils": "^8.39.1",
+ "@typescript-eslint/types": "^8.39.1",
"debug": "^4.3.4"
},
"engines": {
@@ -1580,14 +2086,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.35.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.1.tgz",
- "integrity": "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==",
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.1.tgz",
+ "integrity": "sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.35.1",
- "@typescript-eslint/visitor-keys": "8.35.1"
+ "@typescript-eslint/types": "8.39.1",
+ "@typescript-eslint/visitor-keys": "8.39.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1598,9 +2104,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.35.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.1.tgz",
- "integrity": "sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==",
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.1.tgz",
+ "integrity": "sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1615,14 +2121,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.35.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.1.tgz",
- "integrity": "sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==",
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.1.tgz",
+ "integrity": "sha512-gu9/ahyatyAdQbKeHnhT4R+y3YLtqqHyvkfDxaBYk97EcbfChSJXyaJnIL3ygUv7OuZatePHmQvuH5ru0lnVeA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/typescript-estree": "8.35.1",
- "@typescript-eslint/utils": "8.35.1",
+ "@typescript-eslint/types": "8.39.1",
+ "@typescript-eslint/typescript-estree": "8.39.1",
+ "@typescript-eslint/utils": "8.39.1",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -1639,9 +2146,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.35.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.1.tgz",
- "integrity": "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==",
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.1.tgz",
+ "integrity": "sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1653,16 +2160,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.35.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.1.tgz",
- "integrity": "sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==",
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.1.tgz",
+ "integrity": "sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.35.1",
- "@typescript-eslint/tsconfig-utils": "8.35.1",
- "@typescript-eslint/types": "8.35.1",
- "@typescript-eslint/visitor-keys": "8.35.1",
+ "@typescript-eslint/project-service": "8.39.1",
+ "@typescript-eslint/tsconfig-utils": "8.39.1",
+ "@typescript-eslint/types": "8.39.1",
+ "@typescript-eslint/visitor-keys": "8.39.1",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -1721,16 +2228,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.35.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.1.tgz",
- "integrity": "sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==",
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.1.tgz",
+ "integrity": "sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.35.1",
- "@typescript-eslint/types": "8.35.1",
- "@typescript-eslint/typescript-estree": "8.35.1"
+ "@typescript-eslint/scope-manager": "8.39.1",
+ "@typescript-eslint/types": "8.39.1",
+ "@typescript-eslint/typescript-estree": "8.39.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1745,13 +2252,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.35.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.1.tgz",
- "integrity": "sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==",
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.1.tgz",
+ "integrity": "sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.35.1",
+ "@typescript-eslint/types": "8.39.1",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -1828,7 +2335,6 @@
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"ajv": "^8.0.0"
},
@@ -1886,7 +2392,6 @@
"resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz",
"integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"archiver-utils": "^2.1.0",
"async": "^3.2.4",
@@ -1905,7 +2410,6 @@
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz",
"integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"glob": "^7.1.4",
"graceful-fs": "^4.2.0",
@@ -1927,7 +2431,6 @@
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -1942,15 +2445,13 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/archiver-utils/node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"safe-buffer": "~5.1.0"
}
@@ -1966,8 +2467,13 @@
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
},
"node_modules/asynckit": {
"version": "0.4.0",
@@ -1979,7 +2485,6 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/atomically/-/atomically-2.0.3.tgz",
"integrity": "sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==",
- "peer": true,
"dependencies": {
"stubborn-fs": "^1.2.5",
"when-exit": "^2.1.1"
@@ -2035,15 +2540,13 @@
"url": "https://feross.org/support"
}
],
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/big-integer": {
"version": "1.6.52",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
"integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
"license": "Unlicense",
- "peer": true,
"engines": {
"node": ">=0.6"
}
@@ -2053,7 +2556,6 @@
"resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz",
"integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"buffers": "~0.1.1",
"chainsaw": "~0.1.0"
@@ -2067,7 +2569,6 @@
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"license": "MIT",
- "peer": true,
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
@@ -2078,8 +2579,7 @@
"version": "3.4.7",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz",
"integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/brace-expansion": {
"version": "1.1.12",
@@ -2156,7 +2656,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
@@ -2167,7 +2666,6 @@
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": "*"
}
@@ -2177,7 +2675,6 @@
"resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz",
"integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10"
}
@@ -2186,7 +2683,6 @@
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
"integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==",
- "peer": true,
"engines": {
"node": ">=0.2.0"
}
@@ -2214,9 +2710,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001726",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz",
- "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==",
+ "version": "1.0.30001735",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz",
+ "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==",
"dev": true,
"funding": [
{
@@ -2239,7 +2735,6 @@
"resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
"integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==",
"license": "MIT/X11",
- "peer": true,
"dependencies": {
"traverse": ">=0.3.0 <0.4"
},
@@ -2275,7 +2770,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=8"
}
@@ -2326,7 +2820,6 @@
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz",
"integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"buffer-crc32": "^0.2.13",
"crc32-stream": "^4.0.2",
@@ -2348,7 +2841,6 @@
"resolved": "https://registry.npmjs.org/conf/-/conf-11.0.2.tgz",
"integrity": "sha512-jjyhlQ0ew/iwmtwsS2RaB6s8DBifcE2GYBEaw2SJDUY/slJJbNfY4GlDVzOs/ff8cM/Wua5CikqXgbFl5eu85A==",
"license": "MIT",
- "peer": true,
"dependencies": {
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
@@ -2423,8 +2915,7 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/cosmiconfig": {
"version": "7.1.0",
@@ -2456,7 +2947,6 @@
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
"license": "Apache-2.0",
- "peer": true,
"bin": {
"crc32": "bin/crc32.njs"
},
@@ -2469,7 +2959,6 @@
"resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz",
"integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"crc-32": "^1.2.0",
"readable-stream": "^3.4.0"
@@ -2517,19 +3006,33 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
+ "node_modules/date-fns": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
+ "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
+ "node_modules/date-fns-jalali": {
+ "version": "4.0.0-0",
+ "resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.0.0-0.tgz",
+ "integrity": "sha512-EczB+gWceuWCRlacE4T+WmdP+BV/IUQpjQW9aBa9DNcXkKuZFv3WBDqeP2Ew+6YFBtPRRcH5U22+C6gcpwgG8A==",
+ "license": "MIT"
+ },
"node_modules/dayjs": {
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/debounce-fn": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-5.1.2.tgz",
"integrity": "sha512-Sr4SdOZ4vw6eQDvPYNxHogvrxmCIld/VenC5JbNrFwMiwd7lY/Z18ZFfo+EWNG4DD9nFlAujWAo/wGuOPHmy5A==",
"license": "MIT",
- "peer": true,
"dependencies": {
"mimic-fn": "^4.0.0"
},
@@ -2588,7 +3091,6 @@
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-7.2.0.tgz",
"integrity": "sha512-Ol/IPXUARn9CSbkrdV4VJo7uCy1I3VuSiWCaFSg+8BdUOzF9n3jefIpcgAydvUZbTdEBZs2vEiTiS9m61ssiDA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"type-fest": "^2.11.2"
},
@@ -2618,7 +3120,6 @@
"resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
"integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==",
"license": "BSD-3-Clause",
- "peer": true,
"dependencies": {
"readable-stream": "^2.0.2"
}
@@ -2628,7 +3129,6 @@
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -2643,23 +3143,21 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/duplexer2/node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/electron-to-chromium": {
- "version": "1.5.178",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.178.tgz",
- "integrity": "sha512-wObbz/ar3Bc6e4X5vf0iO8xTN8YAjN/tgiAOJLr7yjYFtP9wAjq8Mb5h0yn6kResir+VYx2DXBj9NNobs0ETSA==",
+ "version": "1.5.202",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.202.tgz",
+ "integrity": "sha512-NxbYjRmiHcHXV1Ws3fWUW+SLb62isauajk45LUJ/HgIOkUA7jLZu/X2Iif+X9FBNK8QkF9Zb4Q2mcwXCcY30mg==",
"dev": true,
"license": "ISC"
},
@@ -2668,7 +3166,6 @@
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"once": "^1.4.0"
}
@@ -2678,7 +3175,6 @@
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
"integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
"license": "MIT",
- "peer": true,
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
@@ -2741,9 +3237,9 @@
}
},
"node_modules/esbuild": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
- "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz",
+ "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -2754,31 +3250,32 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.5",
- "@esbuild/android-arm": "0.25.5",
- "@esbuild/android-arm64": "0.25.5",
- "@esbuild/android-x64": "0.25.5",
- "@esbuild/darwin-arm64": "0.25.5",
- "@esbuild/darwin-x64": "0.25.5",
- "@esbuild/freebsd-arm64": "0.25.5",
- "@esbuild/freebsd-x64": "0.25.5",
- "@esbuild/linux-arm": "0.25.5",
- "@esbuild/linux-arm64": "0.25.5",
- "@esbuild/linux-ia32": "0.25.5",
- "@esbuild/linux-loong64": "0.25.5",
- "@esbuild/linux-mips64el": "0.25.5",
- "@esbuild/linux-ppc64": "0.25.5",
- "@esbuild/linux-riscv64": "0.25.5",
- "@esbuild/linux-s390x": "0.25.5",
- "@esbuild/linux-x64": "0.25.5",
- "@esbuild/netbsd-arm64": "0.25.5",
- "@esbuild/netbsd-x64": "0.25.5",
- "@esbuild/openbsd-arm64": "0.25.5",
- "@esbuild/openbsd-x64": "0.25.5",
- "@esbuild/sunos-x64": "0.25.5",
- "@esbuild/win32-arm64": "0.25.5",
- "@esbuild/win32-ia32": "0.25.5",
- "@esbuild/win32-x64": "0.25.5"
+ "@esbuild/aix-ppc64": "0.25.9",
+ "@esbuild/android-arm": "0.25.9",
+ "@esbuild/android-arm64": "0.25.9",
+ "@esbuild/android-x64": "0.25.9",
+ "@esbuild/darwin-arm64": "0.25.9",
+ "@esbuild/darwin-x64": "0.25.9",
+ "@esbuild/freebsd-arm64": "0.25.9",
+ "@esbuild/freebsd-x64": "0.25.9",
+ "@esbuild/linux-arm": "0.25.9",
+ "@esbuild/linux-arm64": "0.25.9",
+ "@esbuild/linux-ia32": "0.25.9",
+ "@esbuild/linux-loong64": "0.25.9",
+ "@esbuild/linux-mips64el": "0.25.9",
+ "@esbuild/linux-ppc64": "0.25.9",
+ "@esbuild/linux-riscv64": "0.25.9",
+ "@esbuild/linux-s390x": "0.25.9",
+ "@esbuild/linux-x64": "0.25.9",
+ "@esbuild/netbsd-arm64": "0.25.9",
+ "@esbuild/netbsd-x64": "0.25.9",
+ "@esbuild/openbsd-arm64": "0.25.9",
+ "@esbuild/openbsd-x64": "0.25.9",
+ "@esbuild/openharmony-arm64": "0.25.9",
+ "@esbuild/sunos-x64": "0.25.9",
+ "@esbuild/win32-arm64": "0.25.9",
+ "@esbuild/win32-ia32": "0.25.9",
+ "@esbuild/win32-x64": "0.25.9"
}
},
"node_modules/escalade": {
@@ -3033,7 +3530,6 @@
"resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz",
"integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"archiver": "^5.0.0",
"dayjs": "^1.8.34",
@@ -3054,7 +3550,6 @@
"resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz",
"integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@fast-csv/format": "4.3.5",
"@fast-csv/parse": "4.3.6"
@@ -3134,8 +3629,7 @@
"url": "https://opencollective.com/fastify"
}
],
- "license": "BSD-3-Clause",
- "peer": true
+ "license": "BSD-3-Clause"
},
"node_modules/fastq": {
"version": "1.19.1",
@@ -3257,15 +3751,13 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
- "license": "ISC",
- "peer": true
+ "license": "ISC"
},
"node_modules/fsevents": {
"version": "2.3.3",
@@ -3288,7 +3780,6 @@
"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
"deprecated": "This package is no longer supported.",
"license": "ISC",
- "peer": true,
"dependencies": {
"graceful-fs": "^4.1.2",
"inherits": "~2.0.0",
@@ -3361,7 +3852,6 @@
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"license": "ISC",
- "peer": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@@ -3390,6 +3880,28 @@
"node": ">=10.13.0"
}
},
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/globals": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz",
@@ -3419,8 +3931,7 @@
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "license": "ISC",
- "peer": true
+ "license": "ISC"
},
"node_modules/graphemer": {
"version": "1.4.0",
@@ -3503,9 +4014,9 @@
}
},
"node_modules/i18next": {
- "version": "25.3.0",
- "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.3.0.tgz",
- "integrity": "sha512-ZSQIiNGfqSG6yoLHaCvrkPp16UejHI8PCDxFYaNG/1qxtmqNmqEg4JlWKlxkrUmrin2sEjsy+Mjy1TRozBhOgw==",
+ "version": "25.3.6",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.3.6.tgz",
+ "integrity": "sha512-dThZ0CTCM3sUG/qS0ZtQYZQcUI6DtBN8yBHK+SKEqihPcEYmjVWh/YJ4luic73Iq6Uxhp6q7LJJntRK5+1t7jQ==",
"funding": [
{
"type": "individual",
@@ -3581,8 +4092,7 @@
"url": "https://feross.org/support"
}
],
- "license": "BSD-3-Clause",
- "peer": true
+ "license": "BSD-3-Clause"
},
"node_modules/ignore": {
"version": "5.3.2",
@@ -3598,8 +4108,7 @@
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/import-fresh": {
"version": "3.3.1",
@@ -3633,7 +4142,6 @@
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"license": "ISC",
- "peer": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
@@ -3643,8 +4151,7 @@
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "license": "ISC",
- "peer": true
+ "license": "ISC"
},
"node_modules/is-arrayish": {
"version": "0.2.1",
@@ -3672,7 +4179,6 @@
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
"integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
"license": "MIT",
- "peer": true,
"bin": {
"is-docker": "cli.js"
},
@@ -3720,8 +4226,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/isexe": {
"version": "2.0.0",
@@ -3775,18 +4280,16 @@
"license": "MIT"
},
"node_modules/json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true,
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
"node_modules/json-schema-typed": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.1.tgz",
"integrity": "sha512-XQmWYj2Sm4kn4WeTYvmpKEbyPsL7nBsb647c7pMe6l02/yx2+Jfc4dT6UZkEXnIUb5LhD55r2HPsJ1milQ4rDg==",
- "license": "BSD-2-Clause",
- "peer": true
+ "license": "BSD-2-Clause"
},
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
@@ -3813,7 +4316,6 @@
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"license": "(MIT OR GPL-3.0-or-later)",
- "peer": true,
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
@@ -3826,7 +4328,6 @@
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -3841,15 +4342,13 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/jszip/node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"safe-buffer": "~5.1.0"
}
@@ -3869,7 +4368,6 @@
"resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz",
"integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"readable-stream": "^2.0.5"
},
@@ -3882,7 +4380,6 @@
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -3897,15 +4394,13 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/lazystream/node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"safe-buffer": "~5.1.0"
}
@@ -3925,9 +4420,9 @@
}
},
"node_modules/libphonenumber-js": {
- "version": "1.12.10",
- "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.10.tgz",
- "integrity": "sha512-E91vHJD61jekHHR/RF/E83T/CMoaLXT7cwYA75T4gim4FZjnM6hbJjVIGg7chqlSqRsSvQ3izGmOjHy1SQzcGQ==",
+ "version": "1.12.12",
+ "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.12.tgz",
+ "integrity": "sha512-aWVR6xXYYRvnK0v/uIwkf5Lthq9Jpn0N8TISW/oDTWlYB2sOimuiLn9Q26aUw4KxkJoiT8ACdiw44Y8VwKFIfQ==",
"license": "MIT"
},
"node_modules/lie": {
@@ -3935,7 +4430,6 @@
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"immediate": "~3.0.5"
}
@@ -3950,8 +4444,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz",
"integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==",
- "license": "ISC",
- "peer": true
+ "license": "ISC"
},
"node_modules/locate-path": {
"version": "6.0.0",
@@ -3973,79 +4466,68 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/lodash.difference": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
"integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/lodash.escaperegexp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
"integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/lodash.flatten": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
"integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/lodash.groupby": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz",
"integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/lodash.isfunction": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz",
"integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/lodash.isnil": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz",
"integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/lodash.isundefined": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz",
"integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/lodash.merge": {
"version": "4.6.2",
@@ -4058,15 +4540,13 @@
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
"integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/lodash.uniq": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
"integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/loose-envify": {
"version": "1.4.0",
@@ -4149,7 +4629,6 @@
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -4174,7 +4653,6 @@
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -4184,7 +4662,6 @@
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"minimist": "^1.2.6"
},
@@ -4248,8 +4725,7 @@
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz",
"integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/node-releases": {
"version": "2.0.19",
@@ -4263,7 +4739,6 @@
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -4282,7 +4757,6 @@
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
- "peer": true,
"dependencies": {
"wrappy": "1"
}
@@ -4341,8 +4815,7 @@
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
- "license": "(MIT AND Zlib)",
- "peer": true
+ "license": "(MIT AND Zlib)"
},
"node_modules/parent-module": {
"version": "1.0.1",
@@ -4389,7 +4862,6 @@
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -4510,8 +4982,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/prop-types": {
"version": "15.8.1",
@@ -4701,7 +5172,6 @@
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -4716,7 +5186,6 @@
"resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz",
"integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
"license": "Apache-2.0",
- "peer": true,
"dependencies": {
"minimatch": "^5.1.0"
}
@@ -4736,7 +5205,6 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"license": "ISC",
- "peer": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
@@ -4749,7 +5217,6 @@
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -4758,8 +5225,7 @@
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/resolve": {
"version": "1.22.10",
@@ -4807,7 +5273,6 @@
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"license": "ISC",
- "peer": true,
"dependencies": {
"glob": "^7.1.3"
},
@@ -4897,15 +5362,13 @@
"url": "https://feross.org/support"
}
],
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/saxes": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
"integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==",
"license": "ISC",
- "peer": true,
"dependencies": {
"xmlchars": "^2.2.0"
},
@@ -4939,8 +5402,7 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/shebang-command": {
"version": "2.0.0",
@@ -4989,7 +5451,6 @@
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"safe-buffer": "~5.2.0"
}
@@ -5010,8 +5471,7 @@
"node_modules/stubborn-fs": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-1.2.5.tgz",
- "integrity": "sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==",
- "peer": true
+ "integrity": "sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g=="
},
"node_modules/stylis": {
"version": "4.3.6",
@@ -5077,7 +5537,6 @@
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
@@ -5107,11 +5566,14 @@
}
},
"node_modules/tinyglobby/node_modules/fdir": {
- "version": "6.4.6",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
- "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
"peerDependencies": {
"picomatch": "^3 || ^4"
},
@@ -5139,7 +5601,6 @@
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=14.14"
}
@@ -5168,7 +5629,6 @@
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
"integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==",
"license": "MIT/X11",
- "peer": true,
"engines": {
"node": "*"
}
@@ -5190,8 +5650,7 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "license": "0BSD",
- "peer": true
+ "license": "0BSD"
},
"node_modules/type-check": {
"version": "0.4.0",
@@ -5211,7 +5670,6 @@
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
"integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
"license": "(MIT OR CC0-1.0)",
- "peer": true,
"engines": {
"node": ">=12.20"
},
@@ -5234,15 +5692,16 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.35.1",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.1.tgz",
- "integrity": "sha512-xslJjFzhOmHYQzSB/QTeASAHbjmxOGEP6Coh93TXmUBFQoJ1VU35UHIDmG06Jd6taf3wqqC1ntBnCMeymy5Ovw==",
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.1.tgz",
+ "integrity": "sha512-GDUv6/NDYngUlNvwaHM1RamYftxf782IyEDbdj3SeaIHHv8fNQVRC++fITT7kUJV/5rIA/tkoRSSskt6osEfqg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.35.1",
- "@typescript-eslint/parser": "8.35.1",
- "@typescript-eslint/utils": "8.35.1"
+ "@typescript-eslint/eslint-plugin": "8.39.1",
+ "@typescript-eslint/parser": "8.39.1",
+ "@typescript-eslint/typescript-estree": "8.39.1",
+ "@typescript-eslint/utils": "8.39.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -5268,7 +5727,6 @@
"resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz",
"integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"big-integer": "^1.6.17",
"binary": "~0.3.0",
@@ -5287,7 +5745,6 @@
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -5302,15 +5759,13 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/unzipper/node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"safe-buffer": "~5.1.0"
}
@@ -5361,7 +5816,6 @@
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
"license": "MIT",
- "peer": true,
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
@@ -5370,23 +5824,21 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"license": "MIT",
- "peer": true,
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/vite": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.0.tgz",
- "integrity": "sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g==",
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz",
+ "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5459,11 +5911,14 @@
}
},
"node_modules/vite/node_modules/fdir": {
- "version": "6.4.6",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
- "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
"peerDependencies": {
"picomatch": "^3 || ^4"
},
@@ -5515,8 +5970,7 @@
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.4.tgz",
"integrity": "sha512-4rnvd3A1t16PWzrBUcSDZqcAmsUIy4minDXT/CZ8F2mVDgd65i4Aalimgz1aQkRGU0iH5eT5+6Rx2TK8o443Pg==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/which": {
"version": "2.0.2",
@@ -5548,15 +6002,13 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "license": "ISC",
- "peer": true
+ "license": "ISC"
},
"node_modules/xmlchars": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/yallist": {
"version": "3.1.1",
@@ -5598,7 +6050,6 @@
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz",
"integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"archiver-utils": "^3.0.4",
"compress-commons": "^4.1.2",
@@ -5613,7 +6064,6 @@
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz",
"integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"glob": "^7.2.3",
"graceful-fs": "^4.2.0",
diff --git a/package.json b/package.json
index bf3778a..4215562 100644
--- a/package.json
+++ b/package.json
@@ -11,13 +11,18 @@
"preview": "vite preview"
},
"dependencies": {
+ "@date-io/dayjs": "^3.2.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
- "@mui/material": "^7.2.0",
+ "@mui/material": "^7.3.1",
"@mui/stylis-plugin-rtl": "^7.2.0",
- "@rkheftan/harmony-ui": "^0.1.6",
- "@types/stylis": "^4.2.7",
+ "@mui/x-data-grid-premium": "^8.10.0",
+ "@mui/x-date-pickers": "^8.10.0",
+ "@rkheftan/harmony-ui": "^0.1.8",
"axios": "^1.11.0",
+ "date-fns": "^4.1.0",
+ "date-fns-jalali": "^4.0.0-0",
+ "dayjs": "^1.11.13",
"i18next": "^25.3.0",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
@@ -34,8 +39,8 @@
"devDependencies": {
"@eslint/js": "^9.29.0",
"@types/node": "^24.0.10",
- "@types/react": "^19.1.8",
- "@types/react-dom": "^19.1.6",
+ "@types/react": "^19.1.10",
+ "@types/react-dom": "^19.1.7",
"@types/stylis": "^4.2.7",
"@typescript-eslint/eslint-plugin": "^8.35.1",
"@typescript-eslint/parser": "^8.35.1",
diff --git a/public/locales/en/completionForm.json b/public/locales/en/completionForm.json
new file mode 100644
index 0000000..34572a5
--- /dev/null
+++ b/public/locales/en/completionForm.json
@@ -0,0 +1,38 @@
+{
+ "completion": {
+ "title": "Completion of user account information",
+ "description": "Enter your business information",
+ "name": "Name",
+ "familyName": "Family Name",
+ "gender": "Gender",
+ "optionalNationalCode": "National Code(Optional)",
+ "determinePassword": "Determine Password",
+ "password": "Password",
+ "passwordRepetition": "Repeat password",
+ "determineEmail": "Connect your email",
+ "email": "Email",
+ "vericationCodeButton": "Send verification code",
+ "verificationCode": "verification code",
+ "checkCodeButton": "Check code",
+ "registerButton": "Confirm and Register",
+ "man": "male",
+ "woman": "female",
+ "hasNumber": "includes number",
+ "hasMinLength": "at least 8 characters",
+ "hasUpperAndLower": "includes a lowercase and uppercase letter",
+ "hasSpecialChar": "includes sign (!@#$%^&*)",
+ "notCompatibility": "does not match",
+ "emailCorrectForm": "Enter the correct email form.",
+ "agreementPart1": "By continuing the registration process, you agree to the",
+ "agreementLinkText": " Harmony Terms and Conditions",
+ "agreementPart2": ".",
+ "sent": "sent",
+ "country": "country",
+ "dateOfBirth": "Date of birth(optional)",
+ "submitSuccess": "Information successfully registered",
+ "submitError": "Error in registering information",
+ "submitting": "Submitting...",
+ "success": "Success",
+ "agreement": "1. Confidentiality of Information: Harmony commits under no circumstances to disclose users’ identity information, such as phone numbers, email addresses, passwords, user IDs, or any related data, to third parties.\n\n2. User information is used solely for providing authentication services and remains confidential even after account deactivation or termination.\n\n3. Harmony is obliged to implement necessary security measures to prevent unauthorized access.\n\n4. Responsibility for Account Security: Users must protect their accounts and choose strong, non-guessable passwords. Periodic password changes and immediate action in case of suspected unauthorized access are required. Any misuse of the account due to user negligence is the responsibility of the user.\n\n5. Security Breaches and Cyber Attacks: Harmony is not responsible for security breaches caused by cyber attacks beyond the system's control. However, Harmony employs up-to-date security standards and encryption to prevent such incidents.\n\n6. User Negligence in Protecting Information: If account information is disclosed due to user negligence or error, Harmony bears no responsibility. Determining such cases, based on system security logs, is the responsibility of Harmony's technical manager.\n\n7. Accurate Logging of Activities: All events related to registering, editing, and deleting information in the system are accurately and immutably logged. Claims regarding deletion or modification of data without logs are invalid unless supported by documentation provided by the user.\n\n8. Service Updates: Harmony services may be updated or changed over time. Continued use of the system after changes implies acceptance of the new terms. If users disagree, they may request account deletion.\n\n9. User Support: Support is provided only via email and phone, free of charge. Harmony is not obligated to provide in-person support or training beyond basic services.\n\n10. Official Communication Channels: Harmony communicates with users only via the phone number and email registered in the user account. Official announcements are sent through these channels.\n\n11. Official Domains for Communication: All emails from Harmony are sent exclusively from the domain harmony.id. Users must verify this to prevent phishing or similar attacks.\n\n12. Compliance with Iranian Laws: Users must comply with all applicable laws of the Islamic Republic of Iran, including the “Electronic Commerce Law,” “Computer Crimes Law,” and related legislation. Responsibility for violations rests with the user.\n\n13. Temporary Data Retention After Account Termination: Upon account termination or deletion, user information is stored securely for 30 days and permanently deleted thereafter.\n\n14. Ownership of User Data: All data submitted by users belongs to them. Harmony has no ownership over this information. Users are responsible for the accuracy, quality, and legality of their data.\n\n15. Purposeful Use of Identity Information: Collected identity information during registration is used only for authentication and basic services. It will not be shared with any third party without explicit user consent, except under a court order or legal authority.\n\n16. Permanent Data Confidentiality: Harmony commits to maintaining confidentiality of collected information even after the end of the user relationship or account closure.\n\n17. Limitation of Liability: Harmony is not liable for direct or indirect damages resulting from use or inability to use the authentication services.\n\n18. Disruptions in Communication Infrastructure: Harmony is not responsible for disruptions caused by the internet, infrastructure services, or other issues beyond its control.\n\n19. Force Majeure and Unforeseen Events: Harmony bears no responsibility for natural disasters, strikes, power outages, cyber attacks, or other events beyond its control that prevent service delivery.\n\n20. Services Dependent on Third Parties: If parts of the authentication services are provided by third parties, the usage terms of those services are the responsibility of those companies, and Harmony bears no liability.\n\n21. Guarantee of Data Access in Case of Service Termination: If Harmony ceases operations permanently, it commits to keeping servers active for two years and allowing users access to their data.\n\n22. Notification of Service Interruptions: If service interruption is necessary, Harmony must notify users at least 12 hours in advance via email or SMS."
+ }
+}
diff --git a/public/locales/en/country.json b/public/locales/en/country.json
new file mode 100644
index 0000000..b874af3
--- /dev/null
+++ b/public/locales/en/country.json
@@ -0,0 +1,247 @@
+{
+ "country.afghanistan": "Afghanistan",
+ "country.aland_islands": "Aland islands",
+ "country.albania": "Albania",
+ "country.algeria": "Algeria",
+ "country.american_samoa": "American samoa",
+ "country.andorra": "Andorra",
+ "country.angola": "Angola",
+ "country.anguilla": "Anguilla",
+ "country.antarctica": "Antarctica",
+ "country.antigua_and_barbuda": "Antigua and barbuda",
+ "country.argentina": "Argentina",
+ "country.armenia": "Armenia",
+ "country.aruba": "Aruba",
+ "country.australia": "Australia",
+ "country.austria": "Austria",
+ "country.azerbaijan": "Azerbaijan",
+ "country.bahamas": "Bahamas",
+ "country.bahrain": "Bahrain",
+ "country.bangladesh": "Bangladesh",
+ "country.barbados": "Barbados",
+ "country.belarus": "Belarus",
+ "country.belgium": "Belgium",
+ "country.belize": "Belize",
+ "country.benin": "Benin",
+ "country.bermuda": "Bermuda",
+ "country.bhutan": "Bhutan",
+ "country.bolivia": "Bolivia",
+ "country.bosnia_and_herzegovina": "Bosnia and herzegovina",
+ "country.botswana": "Botswana",
+ "country.brazil": "Brazil",
+ "country.british_indian_ocean_territory": "British indian ocean territory",
+ "country.british_virgin_islands": "British virgin islands",
+ "country.brunei": "Brunei",
+ "country.bulgaria": "Bulgaria",
+ "country.burkina_faso": "Burkina faso",
+ "country.burundi": "Burundi",
+ "country.cambodia": "Cambodia",
+ "country.cameroon": "Cameroon",
+ "country.canada": "Canada",
+ "country.cape_verde": "Cape verde",
+ "country.cayman_islands": "Cayman islands",
+ "country.central_african_republic": "Central african republic",
+ "country.chad": "Chad",
+ "country.chile": "Chile",
+ "country.china": "China",
+ "country.christmas_island": "Christmas island",
+ "country.cocos_keeling_islands": "Cocos keeling islands",
+ "country.colombia": "Colombia",
+ "country.comoros": "Comoros",
+ "country.congo_brazzaville": "Congo brazzaville",
+ "country.congo_kinshasa": "Congo kinshasa",
+ "country.cook_islands": "Cook islands",
+ "country.costa_rica": "Costa rica",
+ "country.cote_divoire": "Cote divoire",
+ "country.croatia": "Croatia",
+ "country.cuba": "Cuba",
+ "country.curacao": "Curacao",
+ "country.cyprus": "Cyprus",
+ "country.czech_republic": "Czech republic",
+ "country.denmark": "Denmark",
+ "country.djibouti": "Djibouti",
+ "country.dominica": "Dominica",
+ "country.dominican_republic": "Dominican republic",
+ "country.ecuador": "Ecuador",
+ "country.egypt": "Egypt",
+ "country.el_salvador": "El salvador",
+ "country.equatorial_guinea": "Equatorial guinea",
+ "country.eritrea": "Eritrea",
+ "country.estonia": "Estonia",
+ "country.eswatini": "Eswatini",
+ "country.ethiopia": "Ethiopia",
+ "country.falkland_islands": "Falkland islands",
+ "country.faroe_islands": "Faroe islands",
+ "country.fiji": "Fiji",
+ "country.finland": "Finland",
+ "country.france": "France",
+ "country.french_guiana": "French guiana",
+ "country.french_polynesia": "French polynesia",
+ "country.gabon": "Gabon",
+ "country.gambia": "Gambia",
+ "country.georgia": "Georgia",
+ "country.germany": "Germany",
+ "country.ghana": "Ghana",
+ "country.gibraltar": "Gibraltar",
+ "country.greece": "Greece",
+ "country.greenland": "Greenland",
+ "country.grenada": "Grenada",
+ "country.guadeloupe": "Guadeloupe",
+ "country.guam": "Guam",
+ "country.guatemala": "Guatemala",
+ "country.guernsey": "Guernsey",
+ "country.guinea": "Guinea",
+ "country.guinea_bissau": "Guinea bissau",
+ "country.guyana": "Guyana",
+ "country.haiti": "Haiti",
+ "country.honduras": "Honduras",
+ "country.hong_kong": "Hong kong",
+ "country.hungary": "Hungary",
+ "country.iceland": "Iceland",
+ "country.india": "India",
+ "country.indonesia": "Indonesia",
+ "country.iran": "Iran",
+ "country.iraq": "Iraq",
+ "country.ireland": "Ireland",
+ "country.isle_of_man": "Isle of man",
+ "country.israel": "Israel",
+ "country.italy": "Italy",
+ "country.jamaica": "Jamaica",
+ "country.japan": "Japan",
+ "country.jersey": "Jersey",
+ "country.jordan": "Jordan",
+ "country.kazakhstan": "Kazakhstan",
+ "country.kenya": "Kenya",
+ "country.kiribati": "Kiribati",
+ "country.kosovo": "Kosovo",
+ "country.kuwait": "Kuwait",
+ "country.kyrgyzstan": "Kyrgyzstan",
+ "country.laos": "Laos",
+ "country.latvia": "Latvia",
+ "country.lebanon": "Lebanon",
+ "country.lesotho": "Lesotho",
+ "country.liberia": "Liberia",
+ "country.libya": "Libya",
+ "country.liechtenstein": "Liechtenstein",
+ "country.lithuania": "Lithuania",
+ "country.luxembourg": "Luxembourg",
+ "country.macau": "Macau",
+ "country.madagascar": "Madagascar",
+ "country.malawi": "Malawi",
+ "country.malaysia": "Malaysia",
+ "country.maldives": "Maldives",
+ "country.mali": "Mali",
+ "country.malta": "Malta",
+ "country.marshall_islands": "Marshall islands",
+ "country.martinique": "Martinique",
+ "country.mauritania": "Mauritania",
+ "country.mauritius": "Mauritius",
+ "country.mayotte": "Mayotte",
+ "country.mexico": "Mexico",
+ "country.micronesia": "Micronesia",
+ "country.moldova": "Moldova",
+ "country.monaco": "Monaco",
+ "country.mongolia": "Mongolia",
+ "country.montenegro": "Montenegro",
+ "country.montserrat": "Montserrat",
+ "country.morocco": "Morocco",
+ "country.mozambique": "Mozambique",
+ "country.myanmar": "Myanmar",
+ "country.namibia": "Namibia",
+ "country.nauru": "Nauru",
+ "country.nepal": "Nepal",
+ "country.netherlands": "Netherlands",
+ "country.new_caledonia": "New caledonia",
+ "country.new_zealand": "New zealand",
+ "country.nicaragua": "Nicaragua",
+ "country.niger": "Niger",
+ "country.nigeria": "Nigeria",
+ "country.niue": "Niue",
+ "country.norfolk_island": "Norfolk island",
+ "country.north_korea": "North korea",
+ "country.north_macedonia": "North macedonia",
+ "country.northern_mariana_islands": "Northern mariana islands",
+ "country.norway": "Norway",
+ "country.oman": "Oman",
+ "country.pakistan": "Pakistan",
+ "country.palau": "Palau",
+ "country.palestine": "Palestine",
+ "country.panama": "Panama",
+ "country.papua_new_guinea": "Papua new guinea",
+ "country.paraguay": "Paraguay",
+ "country.peru": "Peru",
+ "country.philippines": "Philippines",
+ "country.pitcairn_islands": "Pitcairn islands",
+ "country.poland": "Poland",
+ "country.portugal": "Portugal",
+ "country.puerto_rico": "Puerto rico",
+ "country.qatar": "Qatar",
+ "country.reunion": "Reunion",
+ "country.romania": "Romania",
+ "country.russia": "Russia",
+ "country.rwanda": "Rwanda",
+ "country.saint_barthelemy": "Saint barthelemy",
+ "country.saint_helena": "Saint helena",
+ "country.saint_kitts_and_nevis": "Saint kitts and nevis",
+ "country.saint_lucia": "Saint lucia",
+ "country.saint_martin": "Saint martin",
+ "country.saint_pierre_and_miquelon": "Saint pierre and miquelon",
+ "country.saint_vincent_and_the_grenadines": "Saint vincent and the grenadines",
+ "country.samoa": "Samoa",
+ "country.san_marino": "San marino",
+ "country.sao_tome_and_principe": "Sao tome and principe",
+ "country.saudi_arabia": "Saudi arabia",
+ "country.senegal": "Senegal",
+ "country.serbia": "Serbia",
+ "country.seychelles": "Seychelles",
+ "country.sierra_leone": "Sierra leone",
+ "country.singapore": "Singapore",
+ "country.sint_maarten": "Sint maarten",
+ "country.slovakia": "Slovakia",
+ "country.slovenia": "Slovenia",
+ "country.solomon_islands": "Solomon islands",
+ "country.somalia": "Somalia",
+ "country.south_africa": "South africa",
+ "country.south_georgia_and_south_sandwich_islands": "South georgia and south sandwich islands",
+ "country.south_korea": "South korea",
+ "country.south_sudan": "South sudan",
+ "country.spain": "Spain",
+ "country.sri_lanka": "Sri lanka",
+ "country.sudan": "Sudan",
+ "country.suriname": "Suriname",
+ "country.svalbard_and_jan_mayen": "Svalbard and jan mayen",
+ "country.sweden": "Sweden",
+ "country.switzerland": "Switzerland",
+ "country.syria": "Syria",
+ "country.taiwan": "Taiwan",
+ "country.tajikistan": "Tajikistan",
+ "country.tanzania": "Tanzania",
+ "country.thailand": "Thailand",
+ "country.timor_leste": "Timor leste",
+ "country.togo": "Togo",
+ "country.tokelau": "Tokelau",
+ "country.tonga": "Tonga",
+ "country.trinidad_and_tobago": "Trinidad and tobago",
+ "country.tunisia": "Tunisia",
+ "country.turkey": "Turkey",
+ "country.turkmenistan": "Turkmenistan",
+ "country.turks_and_caicos_islands": "Turks and caicos islands",
+ "country.tuvalu": "Tuvalu",
+ "country.us_virgin_islands": "Us virgin islands",
+ "country.uganda": "Uganda",
+ "country.ukraine": "Ukraine",
+ "country.united_arab_emirates": "United arab emirates",
+ "country.united_kingdom": "United kingdom",
+ "country.united_states": "United states",
+ "country.uruguay": "Uruguay",
+ "country.uzbekistan": "Uzbekistan",
+ "country.vanuatu": "Vanuatu",
+ "country.vatican_city": "Vatican city",
+ "country.venezuela": "Venezuela",
+ "country.vietnam": "Vietnam",
+ "country.wallis_and_futuna": "Wallis and futuna",
+ "country.western_sahara": "Western sahara",
+ "country.yemen": "Yemen",
+ "country.zambia": "Zambia",
+ "country.zimbabwe": "Zimbabwe"
+}
diff --git a/public/locales/en/setting.json b/public/locales/en/setting.json
new file mode 100644
index 0000000..458690b
--- /dev/null
+++ b/public/locales/en/setting.json
@@ -0,0 +1,128 @@
+{
+ "settingForm": {
+ "titlePersonalInfo": "My Personal Information",
+ "descriptionPersonalInfo": "This information is only for your identification and remains with Harmony.",
+ "titlePhoneNumber": "My phone number",
+ "descriptionPhoneNumber": "This information is only for your identification and remains with Harmony.",
+ "titleSocial": "My email and social medias",
+ "descriptionSocial": "This information is only for your identification and remains with Harmony.",
+ "rejectButton": "Cancel",
+ "saveButton": "Save",
+ "editButton": "Edit",
+ "editPhoneNumber": "Change phone number",
+ "addEmailOrSocialButton": "Add email / social",
+ "addEmailButton": "Add email",
+ "name": "Name",
+ "familyName": "Family Name",
+ "country": "Country",
+ "gender": "Gender",
+ "nationalCode": "National code",
+ "man": "Male",
+ "woman": "Female",
+ "genderPlaceholder": "Male",
+ "newPhoneNumber": "New phone number",
+ "verificationCodeButton": "Send verification code",
+ "verificationCode": "Verification code",
+ "checkCode": "Check code",
+ "successButton": "Confirmed",
+ "email": "Email",
+ "apple": "Apple",
+ "google": "Google",
+ "newEmail": "New email",
+ "dialogHeader": "By activating your email, you can use this email to log in the next time you log in.",
+ "or": "Or",
+ "emailError": "Please enter a valid email.",
+ "profilePicture": "User account image",
+ "allowedFormat": "Allowed formats: PNG, JPEG, GIF (maximum 10 MB)",
+ "uploadPicture": "Upload image",
+ "phoneNumberText": "Your new contact number will replace your previous contact number.",
+ "verb": ".",
+ "notDetermined": "Not determined",
+ "successfulChangePhone": "Phone number changed successfully",
+ "phoneNumberIsInvalid": "Phone number is invalid",
+ "thisFieldIsRequired": "This field is required",
+ "changePicture": "Change picture",
+ "confirmAndSave": "Confirm",
+ "fileSizeError": "Your file exceed the limit",
+ "removePicture": "Remove picture",
+ "failRetrieve": "Failed to retrieve profile data.",
+ "errorFetch": "An error occurred while fetching your profile.",
+ "notLoggedIn": "You are not logged in. Please log in to view your profile.",
+ "unknownError": "An unknown error occurred while saving.",
+ "checkConnection": "Failed to save profile. Please check your connection and try again.",
+ "failFetchPhoneNumber": "Failed to fetch phone number data.",
+ "errorFetchPhoneNumber": "An error occurred while fetching your phone number.",
+ "sendCodeFailed": "Send code failed",
+ "verificationCodeRequired": "Verification code required",
+ "verifyCodeFailed": "Verification of code failed",
+ "changePhoneFailed": "Change of phone number failed",
+ "justNow": "Just now",
+ "failFetchEmail": "Failed to fetch email data",
+ "errorFetchEmail": "An error occurred while fetching your linked accounts.",
+ "emailIsInvalid": "Email is invalid",
+ "changeEmailFailed": "Change of email failed",
+ "anErrorOccurred": "An error occurred.",
+ "saving": "Saving..."
+ },
+
+ "active": {
+ "activeDevices": "Active devices",
+ "activeDevicesCaption": "Watch and manage all your active devices",
+ "deleteDevicesButton": "Remove rest of the devices",
+ "deleteDevice": "Remove device",
+ "currentDevice": "Current device",
+ "minutesAgo": "{{count}} minutes ago",
+ "justNow": "Just now",
+ "notLoggedIn": "You are not logged in",
+ "failFetchActiveSessions": "Failed to fetch active sessions.",
+ "errorFetch": "An error occurred while fetching your active sessions.",
+ "deleting": "Deleting..."
+ },
+
+ "settings": {
+ "title": "Base settings",
+ "description": "Change your base settings",
+ "editButton": "Edit",
+ "rejectButton": "Reject",
+ "saveButton": "Save",
+ "theme": "Theme and color",
+ "light": "Light",
+ "dark": "Dark",
+ "language": "زبان/language",
+ "calendar": "Calendar and date format",
+ "solar": "Solar",
+ "lunar": "Lunar",
+ "christian": "Christian",
+ "iran": "Iran",
+ "saving": "Saving...",
+ "notLoggedIn": "You are not logged in",
+ "failedRetrieve": "Failed to retrieve settings.",
+ "errorFetch": "An error occurred while fetching your settings.",
+ "saveFailed": "Save failed",
+ "invalidSelection": "Invalid selection"
+ },
+
+ "securityForm": {
+ "password": "Password",
+ "determinePassword": "Log in to your Harmony account more easily by setting a strong password.",
+ "addPassword": "Add password",
+ "notDeterminedPassword": "You have not set a password for this account yet.",
+ "newPassword": "New password",
+ "confirmPassword": "Confirm password",
+ "confirm": "Confirm",
+ "hasNumber": "Contains number",
+ "hasMinLength": "at least 8 character",
+ "hasUpperAndLower": "Contains a lowercase and uppercase letter",
+ "hasSpecialChar": "Contains sign (!@#$%^&*)",
+ "notCompatibility": "Confirm password is not the same as password.",
+ "alertSuccess": "Password has successfully changed",
+ "lastChange": "Last change a few seconds ago",
+ "activePassword": "Password is active",
+ "recentLogins": "Recent logins",
+ "description": "In this section, you can see the recent logins to your Harmony account.",
+ "currentDevice": "Current device",
+ "changePassword": "Change password",
+ "currentPassword": "Current password",
+ "forgetPassword": "Forgot your password?"
+ }
+}
diff --git a/public/locales/en/sideMap.json b/public/locales/en/sideMap.json
new file mode 100644
index 0000000..04428f3
--- /dev/null
+++ b/public/locales/en/sideMap.json
@@ -0,0 +1,14 @@
+{
+ "side": {
+ "account": "Account",
+ "personalInfo": "Personal information",
+ "phoneNumber": "Phone number",
+ "email": "Email",
+ "security": "Security",
+ "password": "Password",
+ "verifiedAddress": "Verified addresses",
+ "recentLogins": "Recent logins",
+ "activeDevices": "Active devices",
+ "settings": "Settings"
+ }
+}
diff --git a/public/locales/fa/completionForm.json b/public/locales/fa/completionForm.json
new file mode 100644
index 0000000..273ea69
--- /dev/null
+++ b/public/locales/fa/completionForm.json
@@ -0,0 +1,46 @@
+{
+ "completion": {
+ "title": "تکمیل اطلاعات حساب کاربری",
+ "description": "اطلاعات کسب و کار خود را وارد کنید",
+ "name": "نام",
+ "familyName": "نام خانوادگی",
+ "gender": "جنسیت",
+ "optionalNationalCode": "کدملی(اختیاری)",
+ "determinePassword": "تعیین رمز عبور",
+ "password": "رمز عبور",
+ "passwordRepetition": "تکرار رمز عبور",
+ "determineEmail": "اتصال ایمیل خود",
+ "email": "ایمیل",
+ "vericationCodeButton": "ارسال کد تایید",
+ "verificationCode": "کد تایید",
+ "checkCodeButton": "بررسی کد",
+ "registerButton": "تایید و ثبت نام",
+ "man": "مرد",
+ "woman": "زن",
+ "hasNumber": "شامل عدد",
+ "hasMinLength": "حداقل 8 کاراکتر",
+ "hasUpperAndLower": "شامل یک حرف کوچک و بزرگ",
+ "hasSpecialChar": "شامل علامت (!@#$%^&*)",
+ "notCompatibility": "تکرار رمز عبور با رمز عبور یکسان نمی باشد",
+ "emailCorrectForm": "ساختار ایمیل صحیح نیست",
+ "agreementPart1": " ادامه فرایند ثبت نام به منزله تایید و قبول",
+ "agreementLinkText": " قوانین و مقررات هارمونی",
+ "agreementPart2": "می باشد.",
+ "sent": "ارسال شد!",
+ "country": "کشور",
+ "dateOfBirth": "تاریخ تولد(اختیاری)",
+ "invalidCountry": "کشور انتخاب شده صحیح نیست",
+ "rules": "قوانین و مقررات",
+ "alertSuccess": "ایمیل با موفقیت تایید شد",
+ "submitSuccess": "اطلاعات با موفقیت ثبت شد",
+ "submitError": "خطا در ثبت اطلاعات",
+ "submitting": "در حال تایید...",
+ "success": "موفقیت",
+ "successfullCodeSent": "کد با موفقیت ارسال شد",
+ "codeSentBut": "کد ارسال شد اما",
+ "problem": "مشکلی پیش آمده",
+ "codeVerified": "کد با موفقیت تایید شد",
+ "invalidCode": "کد نامعتبر است",
+ "agreement": "۱. محرمانگی اطلاعات هارمونی متعهد میشود تحت هیچ شرایطی اطلاعات هویتی کاربران نظیر شماره تلفن، ایمیل، رمز عبور، شناسه کاربری و هرگونه داده مرتبط را در اختیار اشخاص ثالث قرار ندهد. اطلاعات کاربران صرفاً در چارچوب ارائه خدمات احراز هویت مورد استفاده قرار گرفته و حتی پس از غیرفعالسازی حساب یا قطع همکاری، این اطلاعات محرمانه باقی خواهد ماند. هارمونی موظف به پیادهسازی تدابیر امنیتی لازم برای جلوگیری از هرگونه دسترسی غیرمجاز میباشد.\n\n۲. مسئولیت حفظ اطلاعات ورود: کاربر موظف است از حساب کاربری خود محافظت کند و رمز عبوری ایمن و غیرقابل حدس انتخاب نماید. تغییر دورهای رمز عبور و اقدام فوری در صورت احساس خطر دسترسی غیرمجاز الزامی است. مسئولیت هرگونه سوءاستفاده از حساب کاربری به دلیل بیاحتیاطی کاربر، بر عهده خود وی خواهد بود.\n\n۳. رخنههای امنیتی و حملات سایبری: هارمونی در برابر رخنههای امنیتی ناشی از حملات سایبری که خارج از کنترل سیستم است، مسئولیتی ندارد. با این حال، هارمونی از بهروزترین استانداردهای امنیتی و رمزنگاری برای جلوگیری از چنین حوادثی بهره میبرد.\n\n۴. قصور کاربر در حفاظت از اطلاعات: چنانچه اطلاعات حساب کاربری به دلیل سهلانگاری یا اشتباه کاربر افشا شود، هارمونی مسئولیتی در این خصوص ندارد. تشخیص چنین مواردی بر اساس لاگهای امنیتی سیستم، بر عهده مدیر فنی هارمونی خواهد بود.\n\n۵. ثبت دقیق فعالیتها در لاگها: تمامی رویدادهای مرتبط با ثبت، ویرایش و حذف اطلاعات در سیستم، بهصورت دقیق و غیرقابلتغییر در لاگ کاربران ثبت میگردد. هرگونه ادعا مبنی بر حذف یا تغییر دادهها بدون ردپای لاگ قابل پذیرش نیست مگر با ارائه مستندات از سوی کاربر.\n\n۶. بهروزرسانی خدمات: خدمات هارمونی ممکن است به مرور زمان بهروزرسانی یا تغییر پیدا کند. ادامه استفاده از سیستم پس از اعمال تغییرات به معنای پذیرش مقررات جدید است. در صورت عدم موافقت، کاربر میتواند درخواست حذف حساب خود را ارائه دهد.\n\n۷. پشتیبانی کاربران: پشتیبانی خدمات صرفاً از طریق ایمیل و تماس تلفنی صورت میگیرد و رایگان است. هارمونی تعهدی به ارائه پشتیبانی حضوری یا آموزشهای فراتر از خدمات پایه ندارد.\n\n۸. راههای ارتباط رسمی: هارمونی تنها از طریق شماره تلفن و ایمیل ثبتشده در حساب کاربر با وی در ارتباط خواهد بود. اطلاعیهها و اعلانات رسمی از این مسیرها انجام میگیرد.\n\n۹. دامنههای رسمی ارتباط: تمام ایمیلهای ارسالی از سوی هارمونی صرفاً با دامنهی harmony.id ارسال میشود. کاربران موظف به بررسی این نشانی برای جلوگیری از فیشینگ و حملات مشابه هستند.\n\n۱۰. رعایت قوانین جمهوری اسلامی ایران: کاربر موظف است در استفاده از سیستم، کلیه قوانین جاری کشور، از جمله «قانون تجارت الکترونیکی»، «قانون جرائم رایانهای» و سایر قوانین مرتبط را رعایت نماید. مسئولیت هرگونه تخلف بر عهده کاربر خواهد بود.\n\n۱۱. نگهداری موقت اطلاعات پس از فسخ حساب: در صورت فسخ یا حذف حساب، اطلاعات کاربر به مدت ۳۰ روز در فضای امن نگهداری میشود و پس از آن، بهطور غیرقابلبازگشت حذف خواهد شد.\n\n۱۲. مالکیت اطلاعات کاربر: تمام اطلاعات ثبتشده توسط کاربر متعلق به خود اوست و هارمونی هیچگونه مالکیتی بر این اطلاعات ندارد. کاربر مسئول صحت، کیفیت و قانونی بودن دادههای خود میباشد.\n\n۱۳. استفاده هدفمند از اطلاعات شناسایی: اطلاعات هویتی جمعآوریشده هنگام ثبتنام تنها برای احراز هویت و ارائه خدمات پایه مورد استفاده قرار میگیرد. این اطلاعات بدون رضایت صریح کاربر، به هیچ نهاد یا شخص ثالثی منتقل نخواهد شد. تبصره: اطلاعات هویتی کاربران صرفاً در صورت حکم مقام قضایی یا مراجع ذیصلاح و در چارچوب قوانین، قابل ارائه خواهد بود.\n\n۱۴. محرمانگی دائمی دادهها: هارمونی متعهد است حتی پس از اتمام رابطه کاربری یا انحلال حساب، اطلاعات جمعآوریشده را به عنوان محرمانه حفظ نماید.\n\n۱۵. محدودیت مسئولیت: هارمونی مسئولیتی در قبال خسارات مستقیم یا غیرمستقیمی که به دلیل استفاده یا عدم استفاده از خدمات احراز هویت ایجاد شود، نخواهد داشت.\n\n۱۶. اختلال در بسترهای ارتباطی: هارمونی در برابر اختلالهای ناشی از شبکه اینترنت، خدمات زیرساختی یا هرگونه مشکل خارج از کنترل خود، مسئولیتی ندارد.\n\n۱۷. حوادث قهری و غیرمترقبه: در صورت وقوع بلایای طبیعی، اعتصاب، قطعی برق، حملات سایبری یا هرگونه رخداد خارج از کنترل هارمونی که مانع ارائه خدمات شود، مسئولیتی متوجه هارمونی نخواهد بود.\n\n۱۸. خدمات وابسته به سایر سرویسها: چنانچه بخشی از خدمات احراز هویت توسط شرکتهای ثالث ارائه شود، قوانین استفاده از این سرویسها بر عهده همان شرکتهاست و هارمونی نسبت به آنها مسئولیتی ندارد.\n\n۱۹. تضمین دسترسی به اطلاعات در صورت توقف فعالیت: در صورت توقف دائمی فعالیت هارمونی، این شرکت متعهد است به مدت دو سال، سرورها را فعال نگه دارد و امکان دسترسی کاربران به اطلاعات خود را فراهم سازد.\n\n۲۰. اطلاعرسانی در مورد قطع سرویسها: در صورت نیاز به توقف خدمات، هارمونی موظف است حداقل ۱۲ ساعت قبل، این موضوع را از طریق ایمیل یا پیامک به اطلاع کاربران برساند."
+ }
+}
diff --git a/public/locales/fa/country.json b/public/locales/fa/country.json
new file mode 100644
index 0000000..0da70cf
--- /dev/null
+++ b/public/locales/fa/country.json
@@ -0,0 +1,182 @@
+{
+ "country.afghanistan": "افغانستان",
+ "country.aland_islands": "جزایر آلند",
+ "country.albania": "آلبانی",
+ "country.algeria": "الجزایر",
+ "country.american_samoa": "ساموای آمریکایی",
+ "country.andorra": "آندورا",
+ "country.angola": "آنگولا",
+ "country.anguilla": "آنگویلا",
+ "country.antarctica": "جنوبگان",
+ "country.antigua_and_barbuda": "آنتیگوا و باربودا",
+ "country.argentina": "آرژانتین",
+ "country.armenia": "ارمنستان",
+ "country.aruba": "آروبا",
+ "country.australia": "استرالیا",
+ "country.austria": "اتریش",
+ "country.azerbaijan": "آذربایجان",
+ "country.bahamas": "باهاما",
+ "country.bahrain": "بحرین",
+ "country.bangladesh": "بنگلادش",
+ "country.barbados": "باربادوس",
+ "country.belarus": "بلاروس",
+ "country.belgium": "بلژیک",
+ "country.belize": "بلیز",
+ "country.benin": "بنین",
+ "country.bermuda": "برمودا",
+ "country.bhutan": "بوتان",
+ "country.bolivia": "بولیوی",
+ "country.bosnia_and_herzegovina": "بوسنی و هرزگوین",
+ "country.botswana": "بوتسوانا",
+ "country.brazil": "برزیل",
+ "country.british_virgin_islands": "جزایر ویرجین بریتانیا",
+ "country.brunei": "برونئی",
+ "country.bulgaria": "بلغارستان",
+ "country.burkina_faso": "بورکینافاسو",
+ "country.burundi": "بوروندی",
+ "country.cambodia": "کامبوج",
+ "country.cameroon": "کامرون",
+ "country.canada": "کانادا",
+ "country.cape_verde": "کیپ ورد",
+ "country.cayman_islands": "جزایر کیمن",
+ "country.central_african_republic": "جمهوری آفریقای مرکزی",
+ "country.chad": "چاد",
+ "country.chile": "شیلی",
+ "country.china": "چین",
+ "country.colombia": "کلمبیا",
+ "country.comoros": "کومور",
+ "country.costa_rica": "کاستاریکا",
+ "country.cote_divoire": "ساحل عاج",
+ "country.croatia": "کرواسی",
+ "country.cuba": "کوبا",
+ "country.cyprus": "قبرس",
+ "country.czech_republic": "جمهوری چک",
+ "country.denmark": "دانمارک",
+ "country.djibouti": "جیبوتی",
+ "country.dominica": "دومینیکا",
+ "country.dominican_republic": "جمهوری دومینیکن",
+ "country.ecuador": "اکوادور",
+ "country.egypt": "مصر",
+ "country.el_salvador": "السالوادور",
+ "country.equatorial_guinea": "گینه استوایی",
+ "country.eritrea": "اریتره",
+ "country.estonia": "استونی",
+ "country.eswatini": "سوازیلند",
+ "country.ethiopia": "اتیوپی",
+ "country.fiji": "فیجی",
+ "country.finland": "فنلاند",
+ "country.france": "فرانسه",
+ "country.gabon": "گابن",
+ "country.gambia": "گامبیا",
+ "country.georgia": "گرجستان",
+ "country.germany": "آلمان",
+ "country.ghana": "غنا",
+ "country.greece": "یونان",
+ "country.guatemala": "گواتمالا",
+ "country.guinea": "گینه",
+ "country.guinea_bissau": "گینه بیسائو",
+ "country.guyana": "گویان",
+ "country.haiti": "هائیتی",
+ "country.honduras": "هندوراس",
+ "country.hungary": "مجارستان",
+ "country.iceland": "ایسلند",
+ "country.india": "هندوستان",
+ "country.indonesia": "اندونزی",
+ "country.iran": "ایران",
+ "country.iraq": "عراق",
+ "country.ireland": "ایرلند",
+ "country.israel": "اسرائیل",
+ "country.italy": "ایتالیا",
+ "country.jamaica": "جامائیکا",
+ "country.japan": "ژاپن",
+ "country.jordan": "اردن",
+ "country.kazakhstan": "قزاقستان",
+ "country.kenya": "کنیا",
+ "country.kuwait": "کویت",
+ "country.kyrgyzstan": "قرقیزستان",
+ "country.laos": "لائوس",
+ "country.latvia": "لتونی",
+ "country.lebanon": "لبنان",
+ "country.lesotho": "لسوتو",
+ "country.liberia": "لیبریا",
+ "country.libya": "لیبی",
+ "country.luxembourg": "لوکزامبورگ",
+ "country.malaysia": "مالزی",
+ "country.maldives": "مالدیو",
+ "country.mali": "مالی",
+ "country.malta": "مالت",
+ "country.mauritania": "موریتانی",
+ "country.mauritius": "موریس",
+ "country.mexico": "مکزیک",
+ "country.moldova": "مولداوی",
+ "country.monaco": "موناکو",
+ "country.mongolia": "مغولستان",
+ "country.morocco": "مراکش",
+ "country.mozambique": "موزامبیک",
+ "country.myanmar": "میانمار",
+ "country.namibia": "نامیبیا",
+ "country.nepal": "نپال",
+ "country.netherlands": "هلند",
+ "country.new_zealand": "نیوزیلند",
+ "country.nicaragua": "نیکاراگوئه",
+ "country.niger": "نیجر",
+ "country.nigeria": "نیجریه",
+ "country.north_korea": "کره شمالی",
+ "country.north_macedonia": "مقدونیه",
+ "country.norway": "نروژ",
+ "country.oman": "عمان",
+ "country.pakistan": "پاکستان",
+ "country.palau": "پالائو",
+ "country.panama": "پاناما",
+ "country.papua_new_guinea": "پاپوآ گینه نو",
+ "country.paraguay": "پاراگوئه",
+ "country.peru": "پرو",
+ "country.philippines": "فیلیپین",
+ "country.poland": "لهستان",
+ "country.portugal": "پرتغال",
+ "country.qatar": "قطر",
+ "country.romania": "رومانی",
+ "country.russia": "روسیه",
+ "country.rwanda": "رواندا",
+ "country.saudi_arabia": "عربستان سعودی",
+ "country.senegal": "سنگال",
+ "country.serbia": "صربستان",
+ "country.seychelles": "سیشل",
+ "country.sierra_leone": "سیرالئون",
+ "country.singapore": "سنگاپور",
+ "country.south_africa": "آفریقای جنوبی",
+ "country.south_korea": "کره جنوبی",
+ "country.south_sudan": "سودان جنوبی",
+ "country.spain": "اسپانیا",
+ "country.sri_lanka": "سریلانکا",
+ "country.sudan": "سودان",
+ "country.suriname": "سورینام",
+ "country.sweden": "سوئد",
+ "country.switzerland": "سوئیس",
+ "country.syria": "سوریه",
+ "country.taiwan": "تایوان",
+ "country.tajikistan": "تاجیکستان",
+ "country.tanzania": "تانزانیا",
+ "country.thailand": "تایلند",
+ "country.timor_leste": "تیمور شرقی",
+ "country.togo": "توگو",
+ "country.tonga": "تونگا",
+ "country.trinidad_and_tobago": "ترینیداد و توباگو",
+ "country.tunisia": "تونس",
+ "country.turkey": "ترکیه",
+ "country.turkmenistan": "ترکمنستان",
+ "country.tuvalu": "تووالو",
+ "country.uganda": "اوگاندا",
+ "country.ukraine": "اوکراین",
+ "country.united_arab_emirates": "امارات متحده عربی",
+ "country.united_kingdom": "انگلستان",
+ "country.united_states": "ایالات متحده آمریکا",
+ "country.uruguay": "اروگوئه",
+ "country.uzbekistan": "ازبکستان",
+ "country.vanuatu": "وانواتو",
+ "country.venezuela": "ونزوئلا",
+ "country.vietnam": "ویتنام",
+ "country.yemen": "یمن",
+ "country.zambia": "زامبیا",
+ "country.zimbabwe": "زیمبابوه"
+}
diff --git a/public/locales/fa/setting.json b/public/locales/fa/setting.json
new file mode 100644
index 0000000..eec7e03
--- /dev/null
+++ b/public/locales/fa/setting.json
@@ -0,0 +1,128 @@
+{
+ "settingForm": {
+ "titlePersonalInfo": "اطلاعات شخصی من",
+ "descriptionPersonalInfo": "این اطلاعات شما صرفا برای احراز هویت شما است و نزد هارمونی باقی میماند",
+ "titlePhoneNumber": "شماره تماس من",
+ "descriptionPhoneNumber": "این اطلاعات شما صرفا برای احراز هویت شما است و نزد هارمونی باقی میماند",
+ "titleSocial": "ایمیل و شبکه های اجتماعی من",
+ "descriptionSocial": "این اطلاعات شما صرفاً برای احراز هویت شما است و نزد هارمونی باقی میماند",
+ "rejectButton": "لغو",
+ "saveButton": "ذخیره",
+ "editButton": "ویرایش",
+ "editPhoneNumber": "تغییر شماره تماس",
+ "addEmailOrSocialButton": "افزودن ایمیل / سوشال",
+ "addEmailButton": "افزودن ایمیل",
+ "name": "نام",
+ "familyName": "نام خانوادگی",
+ "country": "کشور",
+ "gender": "جنسیت",
+ "nationalCode": "کد ملی",
+ "man": "مرد",
+ "woman": "زن",
+ "genderPlaceholder": "مرد",
+ "newPhoneNumber": "شماره تماس جدید",
+ "verificationCodeButton": "ارسال کد تایید",
+ "verificationCode": "کد تایید",
+ "checkCode": "بررسی کد",
+ "successButton": "تایید شد",
+ "email": "ایمیل",
+ "apple": "اپل",
+ "google": "گوگل",
+ "newEmail": "ایمیل جدید",
+ "dialogHeader": "با فعالسازی ایمیل میتوانید در دفعات بعدی ورود برای ورود از این ایمیل استفاده کنید",
+ "or": "یا",
+ "emailError": "لطفا یک ایمیل معتبر وارد کنید",
+ "profilePicture": "تصویر حساب کاربری",
+ "allowedFormat": "فرمتهای مجاز: PNG، JPEG، GIF (حداکثر ۱۰ مگابایت)",
+ "uploadPicture": "بارگذاری تصویر",
+ "phoneNumberText": "شماره تماس جدید شما جایگزین شماره تماس قبلی",
+ "verb": "خواهد شد",
+ "notDetermined": "تعیین نشده",
+ "successfulChangePhone": "شماره تماس با موفقیت تغییر کرد",
+ "phoneNumberIsInvalid": "شماره وارد شده نامعتبر میباشد",
+ "thisFieldIsRequired": "این فیلد الزامی است",
+ "changePicture": "تغییر تصویر",
+ "confirmAndSave": "تایید",
+ "fileSizeError": "حجم فایل شما از حد مجاز بیشتر شده است",
+ "removePicture": "حذف عکس",
+ "failRetrieve": "بازیابی اطلاعات پروفایل ناموفق بود.",
+ "errorFetch": "هنگام دریافت پروفایل شما خطایی رخ داد.",
+ "notLoggedIn": "شما وارد سیستم نشدهاید. لطفا برای مشاهده پروفایل خود وارد شوید.",
+ "unknownError": "هنگام ذخیره خطای ناشناختهای رخ داد.",
+ "checkConnection": "ذخیره پروفایل ناموفق بود. لطفاً اتصال خود را بررسی کرده و دوباره امتحان کنید.",
+ "failFetchPhoneNumber": "دریافت اطلاعات شماره تلفن ناموفق بود.",
+ "errorFetchPhoneNumber": "هنگام دریافت شماره تلفن شما خطایی روی داد.",
+ "sendCodeFailed": "خطا در ارسال کد",
+ "verificationCodeRequired": "کد تأیید مورد نیاز است",
+ "verifyCodeFailed": "تأیید کد ناموفق بود",
+ "changePhoneFailed": "تغییر شماره تلفن ناموفق بود",
+ "justNow": "همین الان",
+ "failFetchEmail": "دریافت دادههای ایمیل ناموفق بود",
+ "errorFetchEmail": "هنگام دریافت حسابهای پیوند داده شده شما خطایی روی داد.",
+ "emailIsInvalid": "ایمیل نامعتبر است",
+ "changeEmailFailed": "تغییر ایمیل با خطا مواجه شد",
+ "anErrorOccurred": "خطایی رخ داد",
+ "saving": "در حال ذخیرهسازی..."
+ },
+
+ "active": {
+ "activeDevices": "نشست های فعال",
+ "activeDevicesCaption": "مشاهده و مدیریت تمام نشست های فعال شما",
+ "deleteDevicesButton": "حذف بقیه نشست ها",
+ "deleteDevice": "حذف نشست",
+ "currentDevice": "دستگاه فعلی",
+ "minutesAgo": "{{count}} دقیقه پیش",
+ "justNow": "همین الان",
+ "notLoggedIn": "شما وارد سیستم نشدهاید",
+ "failFetchActiveSessions": "دریافت نشست های فعال ناموفق بود.",
+ "errorFetch": "هنگام دریافت جلسات فعال شما خطایی روی داد.",
+ "deleting": "در حال حذف..."
+ },
+
+ "settings": {
+ "title": "تنظیمات پایه",
+ "description": "تنظیمات پایهای حساب خود را تغییر دهید",
+ "editButton": "ویرایش",
+ "rejectButton": "لغو",
+ "saveButton": "ذخیره",
+ "theme": "تم و رنگ",
+ "light": "روشن",
+ "dark": "تاریک",
+ "language": "زبان/language",
+ "calendar": "فرمت تقویم و تاریخ",
+ "solar": "شمسی",
+ "lunar": "قمری",
+ "christian": "میلادی",
+ "iran": "ایران",
+ "saving": "در حال ذخیرهسازی...",
+ "notLoggedIn": "شما وارد سیستم نشدهاید",
+ "failedRetrieve": "بازیابی تنظیمات ناموفق بود.",
+ "errorFetch": "هنگام دریافت تنظیمات شما خطایی روی داد.",
+ "saveFailed": "خطا در ذخیره",
+ "invalidSelection": "انتخاب نامعتبر است"
+ },
+
+ "securityForm": {
+ "password": "رمز عبور",
+ "determinePassword": "با تعیین یک رمز عبور قوی راحت تر به اکانت هارمونی خود وارد شوید",
+ "addPassword": "افزودن رمز عبور",
+ "notDeterminedPassword": "هنوز رمز عبوری برای این حساب کاربری تعیین نکرده اید",
+ "newPassword": "رمز عبور جدید",
+ "confirmPassword": "تکرار رمز عبور",
+ "confirm": "تایید",
+ "hasNumber": "شامل عدد",
+ "hasMinLength": "حداقل 8 کاراکتر",
+ "hasUpperAndLower": "شامل یک حرف کوچک و بزرگ",
+ "hasSpecialChar": "شامل علامت (!@#$%^&*)",
+ "notCompatibility": "تکرار رمز عبور با رمز عبور یکسان نمی باشد",
+ "alertSuccess": "رمز عبور با موفقیت تعویض شد",
+ "lastChange": "آخرین تغییر چند ثانیه پیش",
+ "activePassword": "رمز عبور فعال است",
+ "recentLogins": "ورود های اخیر",
+ "description": "در این بخش از ورود های اخیر به اکانت هارمونی خود را مشاهده می کنید",
+ "currentDevice": "دستگاه فعلی",
+ "changePassword": "تغییر رمز عبور",
+ "currentPassword": "رمز عبور فعلی",
+ "forgetPassword": "رمز عبور را فراموش کرده اید؟"
+ }
+}
diff --git a/public/locales/fa/sideMap.json b/public/locales/fa/sideMap.json
new file mode 100644
index 0000000..fd2238a
--- /dev/null
+++ b/public/locales/fa/sideMap.json
@@ -0,0 +1,14 @@
+{
+ "side": {
+ "account": "حساب کاربری",
+ "personalInfo": "اطلاعات شخصی",
+ "phoneNumber": "شماره تماس",
+ "email": "ایمیل",
+ "security": "امنیت",
+ "password": "رمز عبور",
+ "verifiedAddress": "آدرس های تایید شده",
+ "recentLogins": "ورود های اخیر",
+ "activeDevices": "دستگاه های فعال",
+ "settings": "تنظیمات"
+ }
+}
diff --git a/src/components/CardContainer.tsx b/src/components/CardContainer.tsx
new file mode 100644
index 0000000..2159df7
--- /dev/null
+++ b/src/components/CardContainer.tsx
@@ -0,0 +1,63 @@
+import React from 'react';
+import { Box, Typography } from '@mui/material';
+
+interface CardContainerProps {
+ title: string;
+ subtitle: string;
+ action?: React.ReactNode;
+ children?: React.ReactNode;
+ highlighted?: boolean;
+}
+
+export function CardContainer({
+ title,
+ subtitle,
+ action,
+ children,
+ highlighted,
+}: CardContainerProps) {
+ return (
+
+
+
+
+
+ {title}
+
+
+ {subtitle}
+
+
+ {action}
+
+
+ {children}
+
+
+ );
+}
diff --git a/src/components/CountDownTimer.tsx b/src/components/CountDownTimer.tsx
new file mode 100644
index 0000000..6cb280f
--- /dev/null
+++ b/src/components/CountDownTimer.tsx
@@ -0,0 +1,41 @@
+import { useState, useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
+import { toLocaleDigits } from '@/utils/persianDigit';
+
+interface CountdownTimerProps {
+ initialSeconds: number;
+ onComplete?: () => void;
+}
+
+export function CountDownTimer({
+ initialSeconds,
+ onComplete,
+}: CountdownTimerProps) {
+ const { i18n } = useTranslation();
+ const [secondsLeft, setSecondsLeft] = useState(initialSeconds);
+
+ useEffect(() => {
+ setSecondsLeft(initialSeconds);
+
+ const timer = setInterval(() => {
+ setSecondsLeft((prev) => {
+ if (prev <= 1) {
+ clearInterval(timer);
+ onComplete?.();
+ return 0;
+ }
+ return prev - 1;
+ });
+ }, 1000);
+
+ return () => clearInterval(timer);
+ }, [initialSeconds, onComplete]);
+
+ const formatTime = (totalSeconds: number) => {
+ const minutes = String(Math.floor(totalSeconds / 60)).padStart(2, '0');
+ const seconds = String(totalSeconds % 60).padStart(2, '0');
+ return toLocaleDigits(`${minutes}:${seconds}`, i18n.language);
+ };
+
+ return {formatTime(secondsLeft)};
+}
diff --git a/src/components/CountryFlag.tsx b/src/components/CountryFlag.tsx
new file mode 100644
index 0000000..ed1bbee
--- /dev/null
+++ b/src/components/CountryFlag.tsx
@@ -0,0 +1,40 @@
+import { Box, Typography } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+// TODO: move countries outside of feature directory
+import { countries } from '@/features/authentication/data/Countries';
+
+interface CountryFlagProps {
+ code: string;
+}
+
+export function CountryFlag({ code }: CountryFlagProps) {
+ const { t } = useTranslation();
+
+ const countryObj = code ? countries.find((c) => c.code === code) : null;
+
+ if (!countryObj) {
+ return null;
+ }
+
+ const displayName = t(countryObj.label);
+ const flagUrl = `https://flagcdn.com/w40/${countryObj.code.toLowerCase()}.png`;
+
+ return (
+
+
+ {displayName}
+
+ );
+}
diff --git a/src/components/ThemToggle.tsx b/src/components/ThemToggle.tsx
new file mode 100644
index 0000000..0149f26
--- /dev/null
+++ b/src/components/ThemToggle.tsx
@@ -0,0 +1,85 @@
+import { ToggleButtonGroup, ToggleButton, Box } from '@mui/material';
+import { Sun1, Moon } from 'iconsax-react';
+import { useTranslation } from 'react-i18next';
+import { Icon } from '@rkheftan/harmony-ui';
+import { type MouseEvent } from 'react';
+
+enum ThemeMode {
+ Light = 'light',
+ Dark = 'dark',
+}
+
+interface ThemeToggleButtonProps {
+ value: 'light' | 'dark';
+ onChange: (newMode: 'light' | 'dark') => void;
+}
+
+export const ThemeToggleButton = ({
+ value,
+ onChange,
+}: ThemeToggleButtonProps) => {
+ const { t } = useTranslation('setting');
+
+ const handleChange = (
+ _event: MouseEvent,
+ newMode: 'light' | 'dark' | null,
+ ) => {
+ if (newMode !== null) {
+ onChange(newMode);
+ }
+ };
+
+ return (
+
+
+
+
+ {t('settings.light')}
+
+
+
+
+ {t('settings.dark')}
+
+
+
+ );
+};
diff --git a/src/features/authentication/api/userCompletion.ts b/src/features/authentication/api/userCompletion.ts
new file mode 100644
index 0000000..d9fcf26
--- /dev/null
+++ b/src/features/authentication/api/userCompletion.ts
@@ -0,0 +1,32 @@
+import {
+ type SendEmailOtpPayload,
+ type ConfirmEmailOtpPayload,
+ type CompleteUserInfoPayload,
+ type CompleteUserInfoResponse,
+ type GenericApiResponse,
+} from '../types/completionFormApiTypes';
+import apiClient from '@/lib/apiClient';
+
+export const sendEmailOtpApi = async (
+ payload: SendEmailOtpPayload,
+): Promise => {
+ const { data } = await apiClient.post('/User/SendEmailOtp', payload);
+ return data;
+};
+
+export const confirmEmailOtpApi = async (
+ payload: ConfirmEmailOtpPayload,
+): Promise => {
+ const { data } = await apiClient.post('/User/ConfirmEmailOtp', payload);
+ return data;
+};
+
+export const completeUserInformationApi = async (
+ payload: CompleteUserInfoPayload,
+): Promise => {
+ const { data } = await apiClient.post(
+ '/User/CompleteUserInformation',
+ payload,
+ );
+ return data;
+};
diff --git a/src/features/authentication/components/DateOfBirth.tsx b/src/features/authentication/components/DateOfBirth.tsx
new file mode 100644
index 0000000..f219589
--- /dev/null
+++ b/src/features/authentication/components/DateOfBirth.tsx
@@ -0,0 +1,72 @@
+import { useMemo } from 'react';
+import {
+ DatePicker,
+ PickersDay,
+ type PickersDayProps,
+} from '@mui/x-date-pickers';
+import { LocalizationProvider } from '@mui/x-date-pickers';
+import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
+import { AdapterDateFnsJalali } from '@mui/x-date-pickers/AdapterDateFnsJalali';
+import { enUS } from 'date-fns/locale';
+import { faIR as faIRJalali } from 'date-fns-jalali/locale';
+import { getDay } from 'date-fns-jalali';
+import { format as formatJalali } from 'date-fns-jalali';
+import { format } from 'date-fns';
+import { useTranslation } from 'react-i18next';
+import { toLocaleDigits } from '@/utils/persianDigit';
+import { type DateOfBirthProps } from '../types/settingForm';
+
+export default function DateOfBirth({ value, onChange }: DateOfBirthProps) {
+ const { t, i18n } = useTranslation('completionForm');
+ const isFarsi = i18n.language === 'fa' || i18n.language === 'fa-IR';
+
+ const { Adapter, locale, formatString, dayOfWeekFormatter } = useMemo(() => {
+ if (isFarsi) {
+ const persianDays = ['ی', 'د', 'س', 'چ', 'پ', 'ج', 'ش'];
+ return {
+ Adapter: AdapterDateFnsJalali,
+ locale: faIRJalali,
+ formatString: 'yyyy/MM/dd',
+ dayOfWeekFormatter: (date: Date) => persianDays[getDay(date)],
+ };
+ }
+ return {
+ Adapter: AdapterDateFns,
+ locale: enUS,
+ formatString: 'MM/dd/yyyy',
+ dayOfWeekFormatter: undefined,
+ };
+ }, [isFarsi]);
+
+ const CustomDay = (props: PickersDayProps) => {
+ const dayNumber = isFarsi
+ ? formatJalali(props.day, 'dd')
+ : format(props.day, 'dd');
+ return (
+
+ {toLocaleDigits(dayNumber, i18n.language)}
+
+ );
+ };
+
+ return (
+
+
+
+ );
+}
diff --git a/src/features/authentication/components/EmailSection.tsx b/src/features/authentication/components/EmailSection.tsx
new file mode 100644
index 0000000..022126d
--- /dev/null
+++ b/src/features/authentication/components/EmailSection.tsx
@@ -0,0 +1,184 @@
+import React from 'react';
+import {
+ TextField,
+ Box,
+ Button,
+ Switch,
+ FormGroup,
+ Typography,
+ InputAdornment,
+ IconButton,
+ CircularProgress,
+} from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { TickCircle, Edit } from 'iconsax-react';
+import { Icon } from '@rkheftan/harmony-ui';
+import { type EmailSectionProps } from '../types/settingForm';
+
+export function EmailSection({
+ showEmail,
+ setShowEmail,
+ email,
+ setEmail,
+ correctEmail,
+ codeSent,
+ verificationCode,
+ setVerificationCode,
+ buttonState,
+ getButtonLabel,
+ handleSendCode,
+ handleVerifyCode,
+ emailVerified,
+ loading,
+ handleEditEmail,
+}: EmailSectionProps) {
+ const { t } = useTranslation('completionForm');
+
+ const onSendCodeClick = () => {
+ if (!correctEmail) return;
+ handleSendCode();
+ };
+
+ const handleToggleEmail = (e: React.ChangeEvent) => {
+ setShowEmail(e.target.checked);
+ };
+
+ return (
+ <>
+
+
+
+
+ {t('completion.determineEmail')}
+
+
+
+ {showEmail && (
+
+
+ setEmail(e.target.value)}
+ error={email.length > 0 && !correctEmail}
+ sx={{ flex: '1 1 260px' }}
+ slotProps={{
+ input: {
+ startAdornment:
+ !loading && emailVerified ? (
+
+
+
+ ) : null,
+ endAdornment:
+ buttonState === 'counting' ? (
+
+
+
+
+
+ ) : null,
+ sx: {
+ paddingLeft: buttonState === 'counting' ? 0 : undefined,
+ },
+ },
+ }}
+ />
+ {!loading && !emailVerified && (
+
+ )}
+
+ {email && (
+
+ {correctEmail ? '' : t('completion.emailCorrectForm')}
+
+ )}
+ {!emailVerified && codeSent && correctEmail && (
+
+ setVerificationCode(e.target.value)}
+ sx={{ flex: '1 1 260px' }}
+ disabled={loading}
+ />
+
+
+ )}
+
+ )}
+ >
+ );
+}
diff --git a/src/features/authentication/components/PasswordSection.tsx b/src/features/authentication/components/PasswordSection.tsx
new file mode 100644
index 0000000..f8da4e9
--- /dev/null
+++ b/src/features/authentication/components/PasswordSection.tsx
@@ -0,0 +1,250 @@
+import React, { useState } from 'react';
+import {
+ TextField,
+ Box,
+ IconButton,
+ Switch,
+ FormGroup,
+ Typography,
+ InputAdornment,
+} from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { TickCircle, Eye, EyeSlash, CloseCircle } from 'iconsax-react';
+import { PasswordValidationItem } from './PasswordValidation';
+import { Icon } from '@rkheftan/harmony-ui';
+import { type PasswordSectionProps } from '../types/settingForm';
+
+export function PasswordSection({
+ showPasswordSection,
+ setShowPasswordSection,
+ password,
+ setPassword,
+ confirmPassword,
+ setConfirmPassword,
+ matchPassword,
+ hasNumber,
+ hasMinLength,
+ hasUpperAndLower,
+ hasSpecialChar,
+ validPassword,
+ showValidations,
+}: PasswordSectionProps) {
+ const { t } = useTranslation('completionForm');
+ const [showPasswordText, setShowPasswordText] = useState(false);
+ const [showPasswordRepetitionText, setShowPasswordRepetitionText] =
+ useState(false);
+
+ const handleTogglePasswordSection = (
+ e: React.ChangeEvent,
+ ) => {
+ setShowPasswordSection(e.target.checked);
+ };
+
+ const handleTogglePasswordEye = () => setShowPasswordText((prev) => !prev);
+ const handleTogglePasswordRepetitionEye = () =>
+ setShowPasswordRepetitionText((prev) => !prev);
+
+ return (
+ <>
+
+
+
+
+ {t('completion.determinePassword')}
+
+
+
+
+ {showPasswordSection && (
+
+
+ setPassword(e.target.value)}
+ variant="outlined"
+ type={showPasswordText ? 'text' : 'password'}
+ sx={{
+ flex: '1 1 260px',
+ '& .MuiInputBase-input': {
+ pr: 8, // Increased padding to accommodate both icons
+ },
+ }}
+ InputProps={{
+ endAdornment: (
+
+
+
+ {showPasswordText ? (
+
+ ) : (
+
+ )}
+
+ {validPassword && (
+
+ )}
+
+
+ ),
+ }}
+ />
+
+ setConfirmPassword(e.target.value)}
+ error={confirmPassword.length > 0 && !matchPassword}
+ helperText={
+ confirmPassword.length > 0 && !matchPassword
+ ? t('completion.notCompatibility')
+ : ' '
+ }
+ type={showPasswordRepetitionText ? 'text' : 'password'}
+ sx={{
+ flex: '1 1 260px',
+ }}
+ InputProps={{
+ endAdornment: (
+
+
+
+ {showPasswordRepetitionText ? (
+
+ ) : (
+
+ )}
+
+ {confirmPassword.length > 0 && (
+
+ )}
+
+
+ ),
+ }}
+ />
+
+
+ {password && showValidations && (
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ )}
+ >
+ );
+}
diff --git a/src/features/authentication/components/PasswordValidation.tsx b/src/features/authentication/components/PasswordValidation.tsx
new file mode 100644
index 0000000..9f482d7
--- /dev/null
+++ b/src/features/authentication/components/PasswordValidation.tsx
@@ -0,0 +1,39 @@
+import { Box, Typography } from '@mui/material';
+import { TickCircle } from 'iconsax-react';
+import { Icon } from '@rkheftan/harmony-ui';
+import { type ValidationItemProps } from '../types/settingForm';
+
+export function PasswordValidationItem({
+ isValid,
+ label,
+}: ValidationItemProps) {
+ return (
+
+
+
+
+ {label}
+
+
+ );
+}
diff --git a/src/features/authentication/components/PersonalInfoFields.tsx b/src/features/authentication/components/PersonalInfoFields.tsx
new file mode 100644
index 0000000..1f1d8e1
--- /dev/null
+++ b/src/features/authentication/components/PersonalInfoFields.tsx
@@ -0,0 +1,168 @@
+import {
+ TextField,
+ FormControl,
+ InputLabel,
+ MenuItem,
+ Select,
+ Box,
+ Autocomplete,
+ type SelectChangeEvent,
+} from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { Woman, Man } from 'iconsax-react';
+import DateOfBirth from './DateOfBirth';
+import { countries } from '../data/Countries';
+import { CountryFlag } from '@/components/CountryFlag';
+import { Icon } from '@rkheftan/harmony-ui';
+import { type PersonalInfoFieldsProps } from '../types/settingForm';
+import { Gender } from '../types/settingForm';
+
+export function PersonalInfoFields({
+ firstName,
+ setFirstName,
+ lastName,
+ setLastName,
+ nationalId,
+ setNationalId,
+ birthDate,
+ setBirthDate,
+ sex,
+ setSex,
+ country,
+ setCountry,
+}: PersonalInfoFieldsProps) {
+ const { t } = useTranslation('completionForm');
+
+ const countryOptions = countries.map((c) => ({
+ code: c.code,
+ label: t(c.label, { ns: 'countries' }),
+ }));
+
+ const currentCountry = countryOptions.find((c) => c.code === country) || null;
+
+ const handleChangeSex = (e: SelectChangeEvent) => {
+ setSex(e.target.value as Gender);
+ };
+
+ const genderOptions = [
+ {
+ value: Gender.Female,
+ icon: Woman,
+ label: t('completion.woman'),
+ color: '#F50057',
+ },
+ {
+ value: Gender.Male,
+ icon: Man,
+ label: t('completion.man'),
+ color: '#0091EA',
+ },
+ ];
+
+ return (
+
+
+
+ setFirstName(e.target.value)}
+ sx={{ flex: '1 1 260px' }}
+ />
+ setLastName(e.target.value)}
+ sx={{ flex: '1 1 260px' }}
+ />
+
+
+
+
+ {t('completion.gender')}
+
+
+
+ setNationalId(e.target.value)}
+ variant="outlined"
+ sx={{ flex: '1 1 260px' }}
+ />
+
+
+
+ option.label}
+ value={currentCountry}
+ onChange={(_, newValue) => setCountry(newValue?.code || '')}
+ renderOption={(props, option) => (
+
+
+ {option.label}
+
+ )}
+ renderInput={(params) => (
+
+ )}
+ clearOnEscape
+ />
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/features/authentication/components/SubmitSection.tsx b/src/features/authentication/components/SubmitSection.tsx
new file mode 100644
index 0000000..b1c8c07
--- /dev/null
+++ b/src/features/authentication/components/SubmitSection.tsx
@@ -0,0 +1,89 @@
+import { useState } from 'react';
+import {
+ Box,
+ Button,
+ Typography,
+ Link,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+} from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { type SubmitProps } from '../types/settingForm';
+
+export function SubmitSection({ onSubmit, loading, error }: SubmitProps) {
+ const { t, i18n } = useTranslation('completionForm');
+ const [openDialog, setOpenDialog] = useState(false);
+
+ const handleOpenDialog = (e: React.MouseEvent) => {
+ e.preventDefault();
+ setOpenDialog(true);
+ };
+
+ const agreementText = t('completion.agreement');
+ return (
+ <>
+
+
+ {t('completion.agreementPart1')}
+
+ {t('completion.agreementLinkText')}
+
+ {t('completion.agreementPart2')}
+
+
+
+ {error && {error}}
+
+
+
+ >
+ );
+}
diff --git a/src/features/authentication/components/UserCompletionForm.tsx b/src/features/authentication/components/UserCompletionForm.tsx
new file mode 100644
index 0000000..5d220ef
--- /dev/null
+++ b/src/features/authentication/components/UserCompletionForm.tsx
@@ -0,0 +1,329 @@
+import { useEffect, useState } from 'react';
+import { Box, Typography } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import Logo from '@/components/Logo';
+import { PersonalInfoFields } from './PersonalInfoFields';
+import { PasswordSection } from './PasswordSection';
+import { EmailSection } from './EmailSection';
+import { SubmitSection } from './SubmitSection';
+import { useToast } from '@rkheftan/harmony-ui';
+import { regex } from '../../../utils/regex';
+import { toLocaleDigits } from '../../../utils/persianDigit';
+import i18n from '@/config/i18n';
+import { Gender } from '../types/settingForm';
+import { useApi } from '@/hooks/useApi';
+import {
+ sendEmailOtpApi,
+ confirmEmailOtpApi,
+ completeUserInformationApi,
+} from '../api/userCompletion';
+import {
+ type SendEmailOtpPayload,
+ type ConfirmEmailOtpPayload,
+ type CompleteUserInfoPayload,
+} from '../types/completionFormApiTypes';
+import { type ApiResponse } from '@/types/apiResponse';
+
+export function UserCompletionForm() {
+ const { t } = useTranslation('completionForm');
+ const showToast = useToast();
+
+ const [firstName, setFirstName] = useState('');
+ const [lastName, setLastName] = useState('');
+ const [nationalId, setNationalId] = useState('');
+ const [birthDate, setBirthDate] = useState(null);
+ const [sex, setSex] = useState(Gender.Female);
+ const [country, setCountry] = useState('');
+ const [showPasswordSection, setShowPasswordSection] = useState(false);
+ const [password, setPassword] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+ const [showEmail, setShowEmail] = useState(false);
+ const [email, setEmail] = useState('');
+ const [verificationCode, setVerificationCode] = useState('');
+
+ const [codeSent, setCodeSent] = useState(false);
+ const [buttonState, setButtonState] = useState<'default' | 'counting'>(
+ 'default',
+ );
+ const [countdown, setCountdown] = useState(0);
+ const [emailVerified, setEmailVerified] = useState(false);
+ const [showPasswordValidations, setShowPasswordValidations] = useState(false);
+
+ const {
+ hasNumber,
+ hasMinLength,
+ hasUpperAndLower,
+ hasSpecialChar,
+ validPassword,
+ correctEmail,
+ } = regex(password, email);
+ const matchPassword = password === confirmPassword;
+
+ const { execute: sendCode, data: sendCodeData } = useApi(
+ async (payload: SendEmailOtpPayload) => {
+ const result = await sendEmailOtpApi(payload);
+ const conformingResult: ApiResponse = {
+ ...result,
+ errorCode: result.errorCode || 0,
+ validations: [],
+ };
+ return { data: conformingResult };
+ },
+ );
+
+ const {
+ execute: verifyCode,
+ loading: isVerifyingCode,
+ data: verifyCodeData,
+ } = useApi(async (payload: ConfirmEmailOtpPayload) => {
+ const result = await confirmEmailOtpApi(payload);
+ const conformingResult: ApiResponse = {
+ ...result,
+ errorCode: result.errorCode || 0,
+ validations: [],
+ };
+ return { data: conformingResult };
+ });
+
+ const {
+ execute: submitForm,
+ loading: isSubmitting,
+ error: submitError,
+ data: submitData,
+ } = useApi(async (payload: CompleteUserInfoPayload) => {
+ const result = await completeUserInformationApi(payload);
+ const conformingResult: ApiResponse = {
+ ...result,
+ errorCode: result.errorCode || 0,
+ validations: [],
+ };
+ return { data: conformingResult };
+ });
+
+ const getErrorMessage = (error: unknown): string | null => {
+ if (!error) return null;
+ if (error instanceof Error) return error.message;
+ return String(error);
+ };
+
+ useEffect(() => {
+ if (sendCodeData) {
+ if (sendCodeData.success) {
+ showToast({
+ message: sendCodeData.message || t('completion.successfullCodeSent'),
+ severity: 'success',
+ });
+ setCodeSent(true);
+ setButtonState('counting');
+ setCountdown(120);
+ } else {
+ showToast({
+ message: sendCodeData.message || t('completion.problem'),
+ severity: 'error',
+ });
+ }
+ }
+ }, [sendCodeData, showToast, t]);
+
+ useEffect(() => {
+ if (verifyCodeData) {
+ if (verifyCodeData.success) {
+ setEmailVerified(true);
+ showToast({
+ message: verifyCodeData.message || t('completion.codeVerified'),
+ severity: 'success',
+ });
+ } else {
+ showToast({
+ message: verifyCodeData.message || t('completion.invalidCode'),
+ severity: 'error',
+ });
+ setEmailVerified(false);
+ }
+ }
+ }, [verifyCodeData, showToast, t]);
+
+ useEffect(() => {
+ if (submitData) {
+ showToast({
+ message:
+ submitData.message ||
+ t(
+ submitData.success
+ ? 'completion.submitSuccess'
+ : 'completion.submitError',
+ ),
+ severity: submitData.success ? 'success' : 'error',
+ });
+ } else if (submitError) {
+ showToast({
+ message: getErrorMessage(submitError) || t('completion.problem'),
+ severity: 'error',
+ });
+ }
+ }, [submitData, submitError, showToast, t]);
+
+ useEffect(() => {
+ setShowPasswordValidations(password ? !validPassword : false);
+ }, [password, validPassword]);
+
+ useEffect(() => {
+ let timer: NodeJS.Timeout;
+ if (buttonState === 'counting' && countdown > 0) {
+ timer = setInterval(() => {
+ setCountdown((prev) => {
+ if (prev <= 1) {
+ setButtonState('default');
+ clearInterval(timer);
+ return 0;
+ }
+ return prev - 1;
+ });
+ }, 1000);
+ }
+ return () => clearInterval(timer);
+ }, [buttonState, countdown]);
+
+ const handleSendCode = () => {
+ sendCode({ email });
+ };
+
+ const handleVerifyCode = () => {
+ if (!verificationCode.trim()) {
+ showToast({
+ message: 'Please enter the verification code',
+ severity: 'warning',
+ });
+ return;
+ }
+ verifyCode({ email, otpCode: verificationCode });
+ };
+
+ const handleSubmit = () => {
+ submitForm({
+ userId: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
+ firstName,
+ lastName,
+ gender: sex,
+ nationalId,
+ savePassword: showPasswordSection,
+ password: showPasswordSection ? password : undefined,
+ saveEmail: showEmail,
+ email: showEmail ? email : undefined,
+ birthDate,
+ country,
+ });
+ };
+
+ const getButtonLabel = () => {
+ if (buttonState === 'counting') {
+ const m = String(Math.floor(countdown / 60)).padStart(2, '0');
+ const s = String(countdown % 60).padStart(2, '0');
+ return toLocaleDigits(`${m}:${s}`, i18n.language);
+ }
+ return t('completion.vericationCodeButton');
+ };
+
+ const handleEditEmail = () => {
+ setButtonState('default');
+ setCodeSent(false);
+ setEmailVerified(false);
+ setVerificationCode('');
+ };
+
+ return (
+
+
+
+
+
+
+
+ {t('completion.title')}
+
+
+ {t('completion.description')}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/features/authentication/data/Countries.ts b/src/features/authentication/data/Countries.ts
new file mode 100644
index 0000000..2b05f9e
--- /dev/null
+++ b/src/features/authentication/data/Countries.ts
@@ -0,0 +1,271 @@
+export interface Country {
+ code: string;
+ label: string;
+ phone: string;
+}
+
+export interface Country {
+ code: string;
+ label: string;
+ phone: string;
+}
+
+export const countries: readonly Country[] = [
+ { code: 'AF', label: 'country.afghanistan', phone: '+93' },
+ { code: 'AX', label: 'country.aland_islands', phone: '+358' },
+ { code: 'AL', label: 'country.albania', phone: '+355' },
+ { code: 'DZ', label: 'country.algeria', phone: '+213' },
+ { code: 'AS', label: 'country.american_samoa', phone: '+1684' },
+ { code: 'AD', label: 'country.andorra', phone: '+376' },
+ { code: 'AO', label: 'country.angola', phone: '+244' },
+ { code: 'AI', label: 'country.anguilla', phone: '+1264' },
+ { code: 'AQ', label: 'country.antarctica', phone: '+672' },
+ { code: 'AG', label: 'country.antigua_and_barbuda', phone: '+1268' },
+ { code: 'AR', label: 'country.argentina', phone: '+54' },
+ { code: 'AM', label: 'country.armenia', phone: '+374' },
+ { code: 'AW', label: 'country.aruba', phone: '+297' },
+ { code: 'AU', label: 'country.australia', phone: '+61' },
+ { code: 'AT', label: 'country.austria', phone: '+43' },
+ { code: 'AZ', label: 'country.azerbaijan', phone: '+994' },
+ { code: 'BS', label: 'country.bahamas', phone: '+1242' },
+ { code: 'BH', label: 'country.bahrain', phone: '+973' },
+ { code: 'BD', label: 'country.bangladesh', phone: '+880' },
+ { code: 'BB', label: 'country.barbados', phone: '+1246' },
+ { code: 'BY', label: 'country.belarus', phone: '+375' },
+ { code: 'BE', label: 'country.belgium', phone: '+32' },
+ { code: 'BZ', label: 'country.belize', phone: '+501' },
+ { code: 'BJ', label: 'country.benin', phone: '+229' },
+ { code: 'BM', label: 'country.bermuda', phone: '+1441' },
+ { code: 'BT', label: 'country.bhutan', phone: '+975' },
+ { code: 'BO', label: 'country.bolivia', phone: '+591' },
+ { code: 'BA', label: 'country.bosnia_and_herzegovina', phone: '+387' },
+ { code: 'BW', label: 'country.botswana', phone: '+267' },
+ { code: 'BR', label: 'country.brazil', phone: '+55' },
+ {
+ code: 'IO',
+ label: 'country.british_indian_ocean_territory',
+ phone: '+246',
+ },
+ { code: 'VG', label: 'country.british_virgin_islands', phone: '+1284' },
+ { code: 'BN', label: 'country.brunei', phone: '+673' },
+ { code: 'BG', label: 'country.bulgaria', phone: '+359' },
+ { code: 'BF', label: 'country.burkina_faso', phone: '+226' },
+ { code: 'BI', label: 'country.burundi', phone: '+257' },
+ { code: 'KH', label: 'country.cambodia', phone: '+855' },
+ { code: 'CM', label: 'country.cameroon', phone: '+237' },
+ { code: 'CA', label: 'country.canada', phone: '+1' },
+ { code: 'CV', label: 'country.cape_verde', phone: '+238' },
+ { code: 'KY', label: 'country.cayman_islands', phone: '+1345' },
+ { code: 'CF', label: 'country.central_african_republic', phone: '+236' },
+ { code: 'TD', label: 'country.chad', phone: '+235' },
+ { code: 'CL', label: 'country.chile', phone: '+56' },
+ { code: 'CN', label: 'country.china', phone: '+86' },
+ { code: 'CX', label: 'country.christmas_island', phone: '+61' },
+ { code: 'CC', label: 'country.cocos_keeling_islands', phone: '+61' },
+ { code: 'CO', label: 'country.colombia', phone: '+57' },
+ { code: 'KM', label: 'country.comoros', phone: '+269' },
+ { code: 'CG', label: 'country.congo_brazzaville', phone: '+242' },
+ { code: 'CD', label: 'country.congo_kinshasa', phone: '+243' },
+ { code: 'CK', label: 'country.cook_islands', phone: '+682' },
+ { code: 'CR', label: 'country.costa_rica', phone: '+506' },
+ { code: 'CI', label: 'country.cote_divoire', phone: '+225' },
+ { code: 'HR', label: 'country.croatia', phone: '+385' },
+ { code: 'CU', label: 'country.cuba', phone: '+53' },
+ { code: 'CW', label: 'country.curacao', phone: '+599' },
+ { code: 'CY', label: 'country.cyprus', phone: '+357' },
+ { code: 'CZ', label: 'country.czech_republic', phone: '+420' },
+ { code: 'DK', label: 'country.denmark', phone: '+45' },
+ { code: 'DJ', label: 'country.djibouti', phone: '+253' },
+ { code: 'DM', label: 'country.dominica', phone: '+1767' },
+ { code: 'DO', label: 'country.dominican_republic', phone: '+1' },
+ { code: 'EC', label: 'country.ecuador', phone: '+593' },
+ { code: 'EG', label: 'country.egypt', phone: '+20' },
+ { code: 'SV', label: 'country.el_salvador', phone: '+503' },
+ { code: 'GQ', label: 'country.equatorial_guinea', phone: '+240' },
+ { code: 'ER', label: 'country.eritrea', phone: '+291' },
+ { code: 'EE', label: 'country.estonia', phone: '+372' },
+ { code: 'SZ', label: 'country.eswatini', phone: '+268' },
+ { code: 'ET', label: 'country.ethiopia', phone: '+251' },
+ { code: 'FK', label: 'country.falkland_islands', phone: '+500' },
+ { code: 'FO', label: 'country.faroe_islands', phone: '+298' },
+ { code: 'FJ', label: 'country.fiji', phone: '+679' },
+ { code: 'FI', label: 'country.finland', phone: '+358' },
+ { code: 'FR', label: 'country.france', phone: '+33' },
+ { code: 'GF', label: 'country.french_guiana', phone: '+594' },
+ { code: 'PF', label: 'country.french_polynesia', phone: '+689' },
+ { code: 'GA', label: 'country.gabon', phone: '+241' },
+ { code: 'GM', label: 'country.gambia', phone: '+220' },
+ { code: 'GE', label: 'country.georgia', phone: '+995' },
+ { code: 'DE', label: 'country.germany', phone: '+49' },
+ { code: 'GH', label: 'country.ghana', phone: '+233' },
+ { code: 'GI', label: 'country.gibraltar', phone: '+350' },
+ { code: 'GR', label: 'country.greece', phone: '+30' },
+ { code: 'GL', label: 'country.greenland', phone: '+299' },
+ { code: 'GD', label: 'country.grenada', phone: '+1473' },
+ { code: 'GP', label: 'country.guadeloupe', phone: '+590' },
+ { code: 'GU', label: 'country.guam', phone: '+1671' },
+ { code: 'GT', label: 'country.guatemala', phone: '+502' },
+ { code: 'GG', label: 'country.guernsey', phone: '+44' },
+ { code: 'GN', label: 'country.guinea', phone: '+224' },
+ { code: 'GW', label: 'country.guinea_bissau', phone: '+245' },
+ { code: 'GY', label: 'country.guyana', phone: '+592' },
+ { code: 'HT', label: 'country.haiti', phone: '+509' },
+ { code: 'HN', label: 'country.honduras', phone: '+504' },
+ { code: 'HK', label: 'country.hong_kong', phone: '+852' },
+ { code: 'HU', label: 'country.hungary', phone: '+36' },
+ { code: 'IS', label: 'country.iceland', phone: '+354' },
+ { code: 'IN', label: 'country.india', phone: '+91' },
+ { code: 'ID', label: 'country.indonesia', phone: '+62' },
+ { code: 'IR', label: 'country.iran', phone: '+98' },
+ { code: 'IQ', label: 'country.iraq', phone: '+964' },
+ { code: 'IE', label: 'country.ireland', phone: '+353' },
+ { code: 'IM', label: 'country.isle_of_man', phone: '+44' },
+ { code: 'IL', label: 'country.israel', phone: '+972' },
+ { code: 'IT', label: 'country.italy', phone: '+39' },
+ { code: 'JM', label: 'country.jamaica', phone: '+1876' },
+ { code: 'JP', label: 'country.japan', phone: '+81' },
+ { code: 'JE', label: 'country.jersey', phone: '+44' },
+ { code: 'JO', label: 'country.jordan', phone: '+962' },
+ { code: 'KZ', label: 'country.kazakhstan', phone: '+7' },
+ { code: 'KE', label: 'country.kenya', phone: '+254' },
+ { code: 'KI', label: 'country.kiribati', phone: '+686' },
+ { code: 'XK', label: 'country.kosovo', phone: '+383' },
+ { code: 'KW', label: 'country.kuwait', phone: '+965' },
+ { code: 'KG', label: 'country.kyrgyzstan', phone: '+996' },
+ { code: 'LA', label: 'country.laos', phone: '+856' },
+ { code: 'LV', label: 'country.latvia', phone: '+371' },
+ { code: 'LB', label: 'country.lebanon', phone: '+961' },
+ { code: 'LS', label: 'country.lesotho', phone: '+266' },
+ { code: 'LR', label: 'country.liberia', phone: '+231' },
+ { code: 'LY', label: 'country.libya', phone: '+218' },
+ { code: 'LI', label: 'country.liechtenstein', phone: '+423' },
+ { code: 'LT', label: 'country.lithuania', phone: '+370' },
+ { code: 'LU', label: 'country.luxembourg', phone: '+352' },
+ { code: 'MO', label: 'country.macau', phone: '+853' },
+ { code: 'MG', label: 'country.madagascar', phone: '+261' },
+ { code: 'MW', label: 'country.malawi', phone: '+265' },
+ { code: 'MY', label: 'country.malaysia', phone: '+60' },
+ { code: 'MV', label: 'country.maldives', phone: '+960' },
+ { code: 'ML', label: 'country.mali', phone: '+223' },
+ { code: 'MT', label: 'country.malta', phone: '+356' },
+ { code: 'MH', label: 'country.marshall_islands', phone: '+692' },
+ { code: 'MQ', label: 'country.martinique', phone: '+596' },
+ { code: 'MR', label: 'country.mauritania', phone: '+222' },
+ { code: 'MU', label: 'country.mauritius', phone: '+230' },
+ { code: 'YT', label: 'country.mayotte', phone: '+262' },
+ { code: 'MX', label: 'country.mexico', phone: '+52' },
+ { code: 'FM', label: 'country.micronesia', phone: '+691' },
+ { code: 'MD', label: 'country.moldova', phone: '+373' },
+ { code: 'MC', label: 'country.monaco', phone: '+377' },
+ { code: 'MN', label: 'country.mongolia', phone: '+976' },
+ { code: 'ME', label: 'country.montenegro', phone: '+382' },
+ { code: 'MS', label: 'country.montserrat', phone: '+1664' },
+ { code: 'MA', label: 'country.morocco', phone: '+212' },
+ { code: 'MZ', label: 'country.mozambique', phone: '+258' },
+ { code: 'MM', label: 'country.myanmar', phone: '+95' },
+ { code: 'NA', label: 'country.namibia', phone: '+264' },
+ { code: 'NR', label: 'country.nauru', phone: '+674' },
+ { code: 'NP', label: 'country.nepal', phone: '+977' },
+ { code: 'NL', label: 'country.netherlands', phone: '+31' },
+ { code: 'NC', label: 'country.new_caledonia', phone: '+687' },
+ { code: 'NZ', label: 'country.new_zealand', phone: '+64' },
+ { code: 'NI', label: 'country.nicaragua', phone: '+505' },
+ { code: 'NE', label: 'country.niger', phone: '+227' },
+ { code: 'NG', label: 'country.nigeria', phone: '+234' },
+ { code: 'NU', label: 'country.niue', phone: '+683' },
+ { code: 'NF', label: 'country.norfolk_island', phone: '+672' },
+ { code: 'KP', label: 'country.north_korea', phone: '+850' },
+ { code: 'MK', label: 'country.north_macedonia', phone: '+389' },
+ { code: 'MP', label: 'country.northern_mariana_islands', phone: '+1670' },
+ { code: 'NO', label: 'country.norway', phone: '+47' },
+ { code: 'OM', label: 'country.oman', phone: '+968' },
+ { code: 'PK', label: 'country.pakistan', phone: '+92' },
+ { code: 'PW', label: 'country.palau', phone: '+680' },
+ { code: 'PS', label: 'country.palestine', phone: '+970' },
+ { code: 'PA', label: 'country.panama', phone: '+507' },
+ { code: 'PG', label: 'country.papua_new_guinea', phone: '+675' },
+ { code: 'PY', label: 'country.paraguay', phone: '+595' },
+ { code: 'PE', label: 'country.peru', phone: '+51' },
+ { code: 'PH', label: 'country.philippines', phone: '+63' },
+ { code: 'PN', label: 'country.pitcairn_islands', phone: '+64' },
+ { code: 'PL', label: 'country.poland', phone: '+48' },
+ { code: 'PT', label: 'country.portugal', phone: '+351' },
+ { code: 'PR', label: 'country.puerto_rico', phone: '+1' },
+ { code: 'QA', label: 'country.qatar', phone: '+974' },
+ { code: 'RE', label: 'country.reunion', phone: '+262' },
+ { code: 'RO', label: 'country.romania', phone: '+40' },
+ { code: 'RU', label: 'country.russia', phone: '+7' },
+ { code: 'RW', label: 'country.rwanda', phone: '+250' },
+ { code: 'BL', label: 'country.saint_barthelemy', phone: '+590' },
+ { code: 'SH', label: 'country.saint_helena', phone: '+290' },
+ { code: 'KN', label: 'country.saint_kitts_and_nevis', phone: '+1869' },
+ { code: 'LC', label: 'country.saint_lucia', phone: '+1758' },
+ { code: 'MF', label: 'country.saint_martin', phone: '+590' },
+ { code: 'PM', label: 'country.saint_pierre_and_miquelon', phone: '+508' },
+ {
+ code: 'VC',
+ label: 'country.saint_vincent_and_the_grenadines',
+ phone: '+1784',
+ },
+ { code: 'WS', label: 'country.samoa', phone: '+685' },
+ { code: 'SM', label: 'country.san_marino', phone: '+378' },
+ { code: 'ST', label: 'country.sao_tome_and_principe', phone: '+239' },
+ { code: 'SA', label: 'country.saudi_arabia', phone: '+966' },
+ { code: 'SN', label: 'country.senegal', phone: '+221' },
+ { code: 'RS', label: 'country.serbia', phone: '+381' },
+ { code: 'SC', label: 'country.seychelles', phone: '+248' },
+ { code: 'SL', label: 'country.sierra_leone', phone: '+232' },
+ { code: 'SG', label: 'country.singapore', phone: '+65' },
+ { code: 'SX', label: 'country.sint_maarten', phone: '+1721' },
+ { code: 'SK', label: 'country.slovakia', phone: '+421' },
+ { code: 'SI', label: 'country.slovenia', phone: '+386' },
+ { code: 'SB', label: 'country.solomon_islands', phone: '+677' },
+ { code: 'SO', label: 'country.somalia', phone: '+252' },
+ { code: 'ZA', label: 'country.south_africa', phone: '+27' },
+ {
+ code: 'GS',
+ label: 'country.south_georgia_and_south_sandwich_islands',
+ phone: '+500',
+ },
+ { code: 'KR', label: 'country.south_korea', phone: '+82' },
+ { code: 'SS', label: 'country.south_sudan', phone: '+211' },
+ { code: 'ES', label: 'country.spain', phone: '+34' },
+ { code: 'LK', label: 'country.sri_lanka', phone: '+94' },
+ { code: 'SD', label: 'country.sudan', phone: '+249' },
+ { code: 'SR', label: 'country.suriname', phone: '+597' },
+ { code: 'SJ', label: 'country.svalbard_and_jan_mayen', phone: '+47' },
+ { code: 'SE', label: 'country.sweden', phone: '+46' },
+ { code: 'CH', label: 'country.switzerland', phone: '+41' },
+ { code: 'SY', label: 'country.syria', phone: '+963' },
+ { code: 'TW', label: 'country.taiwan', phone: '+886' },
+ { code: 'TJ', label: 'country.tajikistan', phone: '+992' },
+ { code: 'TZ', label: 'country.tanzania', phone: '+255' },
+ { code: 'TH', label: 'country.thailand', phone: '+66' },
+ { code: 'TL', label: 'country.timor_leste', phone: '+670' },
+ { code: 'TG', label: 'country.togo', phone: '+228' },
+ { code: 'TK', label: 'country.tokelau', phone: '+690' },
+ { code: 'TO', label: 'country.tonga', phone: '+676' },
+ { code: 'TT', label: 'country.trinidad_and_tobago', phone: '+1868' },
+ { code: 'TN', label: 'country.tunisia', phone: '+216' },
+ { code: 'TR', label: 'country.turkey', phone: '+90' },
+ { code: 'TM', label: 'country.turkmenistan', phone: '+993' },
+ { code: 'TC', label: 'country.turks_and_caicos_islands', phone: '+1649' },
+ { code: 'TV', label: 'country.tuvalu', phone: '+688' },
+ { code: 'VI', label: 'country.us_virgin_islands', phone: '+1340' },
+ { code: 'UG', label: 'country.uganda', phone: '+256' },
+ { code: 'UA', label: 'country.ukraine', phone: '+380' },
+ { code: 'AE', label: 'country.united_arab_emirates', phone: '+971' },
+ { code: 'GB', label: 'country.united_kingdom', phone: '+44' },
+ { code: 'US', label: 'country.united_states', phone: '+1' },
+ { code: 'UY', label: 'country.uruguay', phone: '+598' },
+ { code: 'UZ', label: 'country.uzbekistan', phone: '+998' },
+ { code: 'VU', label: 'country.vanuatu', phone: '+678' },
+ { code: 'VA', label: 'country.vatican_city', phone: '+39' },
+ { code: 'VE', label: 'country.venezuela', phone: '+58' },
+ { code: 'VN', label: 'country.vietnam', phone: '+84' },
+ { code: 'WF', label: 'country.wallis_and_futuna', phone: '+681' },
+ { code: 'EH', label: 'country.western_sahara', phone: '+212' },
+ { code: 'YE', label: 'country.yemen', phone: '+967' },
+ { code: 'ZM', label: 'country.zambia', phone: '+260' },
+ { code: 'ZW', label: 'country.zimbabwe', phone: '+263' },
+];
diff --git a/src/features/authentication/types/completionFormApiTypes.ts b/src/features/authentication/types/completionFormApiTypes.ts
new file mode 100644
index 0000000..303deca
--- /dev/null
+++ b/src/features/authentication/types/completionFormApiTypes.ts
@@ -0,0 +1,32 @@
+export interface GenericApiResponse {
+ success: boolean;
+ message: string;
+ errorCode?: number;
+}
+
+export interface SendEmailOtpPayload {
+ email: string;
+}
+
+export interface ConfirmEmailOtpPayload {
+ email: string;
+ otpCode: string;
+}
+
+export interface CompleteUserInfoPayload {
+ userId: string;
+ firstName: string;
+ lastName: string;
+ gender: 0 | 1 | 2;
+ nationalId: string;
+ birthDate: Date | null;
+ country: string;
+ savePassword?: boolean;
+ password?: string;
+ saveEmail?: boolean;
+ email?: string;
+}
+
+export interface CompleteUserInfoResponse extends GenericApiResponse {
+ validations: { property: string; message: string }[] | null;
+}
diff --git a/src/features/authentication/types/settingForm.ts b/src/features/authentication/types/settingForm.ts
new file mode 100644
index 0000000..eb9453f
--- /dev/null
+++ b/src/features/authentication/types/settingForm.ts
@@ -0,0 +1,73 @@
+import { type Dispatch, type SetStateAction } from 'react';
+
+export enum Gender {
+ None = 0,
+ Female = 1,
+ Male = 2,
+}
+
+export interface DateOfBirthProps {
+ value: Date | null;
+ onChange: (date: Date | null) => void;
+}
+
+export interface EmailSectionProps {
+ showEmail: boolean;
+ setShowEmail: (checked: boolean) => void;
+ email: string;
+ setEmail: (email: string) => void;
+ correctEmail: boolean;
+ codeSent: boolean;
+ verificationCode: string;
+ setVerificationCode: (code: string) => void;
+ buttonState: 'default' | 'counting' | 'sent';
+ getButtonLabel: () => string;
+ handleSendCode: () => void;
+ handleVerifyCode: () => void;
+ emailVerified: boolean;
+ loading: boolean;
+ handleEditEmail: () => void;
+}
+
+export interface PasswordSectionProps {
+ showPasswordSection: boolean;
+ setShowPasswordSection: (checked: boolean) => void;
+ password: string;
+ setPassword: (password: string) => void;
+ confirmPassword: string;
+ setConfirmPassword: (confirmPassword: string) => void;
+ matchPassword: boolean;
+ hasNumber: boolean;
+ hasMinLength: boolean;
+ hasUpperAndLower: boolean;
+ hasSpecialChar: boolean;
+ validPassword: boolean;
+ showValidations: boolean;
+}
+
+export interface ValidationItemProps {
+ isValid: boolean;
+ label: string;
+}
+
+export interface PersonalInfoFieldsProps {
+ firstName: string;
+ setFirstName: (v: string) => void;
+ lastName: string;
+ setLastName: (v: string) => void;
+ sex: Gender;
+ setSex: Dispatch>;
+ country: string;
+ setCountry: (country: string) => void;
+ nationalId: string;
+ setNationalId: (v: string) => void;
+ birthDate: Date | null;
+ setBirthDate: (d: Date | null) => void;
+}
+
+export interface SubmitProps {
+ onSubmit: () => void;
+ loading: boolean;
+ error: string | null;
+ success: boolean;
+}
diff --git a/src/features/profile/api/settingsApi.ts b/src/features/profile/api/settingsApi.ts
new file mode 100644
index 0000000..3e86b44
--- /dev/null
+++ b/src/features/profile/api/settingsApi.ts
@@ -0,0 +1,173 @@
+import apiClient from '@/lib/apiClient';
+
+import {
+ type GetProfileApiResponse,
+ type SaveProfileApiResponse,
+ type PhoneNumberApiResponse,
+ type ConfirmPhoneNumberApiResponse,
+ type SaveSettingsApiResponse,
+ type DeleteSessionsApiResponse,
+ type PasswordApiResponse,
+ type SendEmailCodeApiResponse,
+ type ConfirmEmailCodeApiResponse,
+ type ChangeEmailApiResponse,
+} from '../types/settingsApiType';
+import { type InfoRowData } from '../types/settingsType';
+
+export async function fetchProfile(): Promise<{ data: GetProfileApiResponse }> {
+ const res = await apiClient.post(
+ '/Profile/GetProfile',
+ {},
+ );
+ return { data: res.data };
+}
+
+export async function saveProfile(payload?: {
+ data: InfoRowData;
+ imageUrl: string | null;
+}): Promise<{ data: SaveProfileApiResponse }> {
+ if (!payload) {
+ throw new Error('Payload for saving profile is missing.');
+ }
+ const res = await apiClient.post(
+ '/Profile/SaveProfilePersonalInforamtion',
+ payload,
+ );
+ return { data: res.data };
+}
+
+export async function sendVerificationCode(payload?: {
+ phoneNumber: string;
+}): Promise<{ data: PhoneNumberApiResponse }> {
+ if (!payload) {
+ throw new Error('Payload for sending verification code is missing.');
+ }
+ const res = await apiClient.post(
+ '/Profile/SendVerfiyPhoneNumberCode',
+ payload,
+ );
+ return { data: res.data };
+}
+
+export async function confirmPhoneNumberCode(payload?: {
+ phoneNumber: string;
+ verifyCode: string;
+}): Promise<{ data: ConfirmPhoneNumberApiResponse }> {
+ if (!payload) {
+ throw new Error('Payload for confirming phone number is missing.');
+ }
+ const res = await apiClient.post(
+ '/Profile/ConfirmPhoneNumberChangeCode',
+ payload,
+ );
+ return { data: res.data };
+}
+
+export async function changePhoneNumber(payload?: {
+ phoneNumber: string;
+}): Promise<{ data: PhoneNumberApiResponse }> {
+ if (!payload) {
+ throw new Error('Payload for changing phone number is missing.');
+ }
+ const res = await apiClient.post(
+ '/Profile/ChangePhoneNumber',
+ payload,
+ );
+ return { data: res.data };
+}
+
+export async function saveSettings(payload?: {
+ theme: number;
+ calendarType: number;
+ language: number;
+}): Promise<{ data: SaveSettingsApiResponse }> {
+ if (!payload) {
+ throw new Error('Payload for saving settings is missing.');
+ }
+ const res = await apiClient.post(
+ '/Profile/SaveSetting',
+ payload,
+ );
+ return { data: res.data };
+}
+
+export async function deleteSessions(payload?: {
+ keys: string[];
+}): Promise<{ data: DeleteSessionsApiResponse }> {
+ if (!payload || payload.keys.length === 0) {
+ throw new Error('Payload with session keys is missing or empty.');
+ }
+ const res = await apiClient.post(
+ '/Profile/DeleteSessions',
+ payload,
+ );
+ return { data: res.data };
+}
+
+export async function addPassword(payload?: {
+ password: string;
+}): Promise<{ data: PasswordApiResponse }> {
+ if (!payload) {
+ throw new Error('Payload for adding password is missing.');
+ }
+ const res = await apiClient.post(
+ '/Profile/AddPassword',
+ payload,
+ );
+ return { data: res.data };
+}
+
+export async function changePassword(payload?: {
+ oldPassword: string;
+ newPassword: string;
+}): Promise<{ data: PasswordApiResponse }> {
+ if (!payload) {
+ throw new Error('Payload for changing password is missing.');
+ }
+ const res = await apiClient.post(
+ '/Profile/ChangePassword',
+ payload,
+ );
+ return { data: res.data };
+}
+
+// ✅ NEW FUNCTIONS
+export async function sendEmailCode(payload?: {
+ email: string;
+}): Promise<{ data: SendEmailCodeApiResponse }> {
+ if (!payload) {
+ throw new Error('Payload for sending email code is missing.');
+ }
+ const res = await apiClient.post(
+ 'Profile/SendEmailChangeCode',
+ payload,
+ );
+ return { data: res.data };
+}
+
+export async function confirmEmailCode(payload?: {
+ email: string;
+ verifyCode: string;
+}): Promise<{ data: ConfirmEmailCodeApiResponse }> {
+ if (!payload) {
+ throw new Error('Payload for confirming email code is missing.');
+ }
+ const res = await apiClient.post(
+ 'Profile/ConfirmEmailChangeCode',
+ payload,
+ );
+ return { data: res.data };
+}
+
+export async function changeEmail(payload?: {
+ email: string;
+}): Promise<{ data: ChangeEmailApiResponse }> {
+ if (!payload) {
+ throw new Error('Payload for changing email is missing.');
+ }
+ const res = await apiClient.post(
+ 'Profile/ChangeEmail',
+ payload,
+ );
+ return { data: res.data };
+}
diff --git a/src/features/profile/components/CountryCodeSelector.tsx b/src/features/profile/components/CountryCodeSelector.tsx
new file mode 100644
index 0000000..8a82727
--- /dev/null
+++ b/src/features/profile/components/CountryCodeSelector.tsx
@@ -0,0 +1,254 @@
+import {
+ Box,
+ InputAdornment,
+ ListItem,
+ ListItemIcon,
+ ListItemText,
+ Menu,
+ MenuItem,
+ TextField,
+ Typography,
+} from '@mui/material';
+import { useMemo, useRef, useState, type RefObject } from 'react';
+import { ArrowDown2 } from 'iconsax-react';
+import ReactCountryFlag from 'react-country-flag';
+import { useTranslation } from 'react-i18next';
+import { countries, type Country } from '../data/countries';
+
+interface CountryCodeSelectorProps {
+ show: boolean;
+ value: string;
+ onChange: (newValue: string) => void;
+ menuAnchor: HTMLElement | null;
+ onCloseFocusRef: RefObject;
+}
+
+/**
+ * An animated country code adornment that fades and slides into view.
+ * Its visibility is controlled by the `show` prop.
+ */
+export function CountryCodeSelector({
+ show,
+ value,
+ onChange,
+ menuAnchor,
+ onCloseFocusRef,
+}: CountryCodeSelectorProps) {
+ const [anchorEl, setAnchorEl] = useState(null);
+ const [searchTerm, setSearchTerm] = useState('');
+ const open = Boolean(anchorEl);
+ const searchInputRef = useRef(null);
+ const menuWidth = menuAnchor ? menuAnchor.clientWidth : 'auto';
+ const { t, i18n } = useTranslation();
+
+ const selectedCountry =
+ countries.find((c) => c.phone === value) || countries[0];
+
+ const handleClick = () => {
+ setAnchorEl(menuAnchor);
+ };
+
+ const handleClose = () => {
+ setTimeout(() => {
+ setAnchorEl(null);
+ }, 0);
+ setTimeout(() => {
+ onCloseFocusRef.current?.focus();
+ }, 100);
+ setSearchTerm(''); // Reset search on close
+ };
+
+ const handleSelect = (country: Country) => {
+ onChange(country.phone);
+ handleClose();
+ };
+
+ const handleMenuEntered = () => {
+ // Focus the input field after the menu has finished opening
+ searchInputRef.current?.focus();
+ };
+
+ const filteredCountries = useMemo(
+ () =>
+ countries.filter(
+ (country) =>
+ t(country.label).toLowerCase().includes(searchTerm.toLowerCase()) ||
+ country.label.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ country.phone.includes(searchTerm),
+ ),
+ [searchTerm, t],
+ );
+
+ return (
+
+
+ theme.transitions.create(['width', 'opacity'], {
+ duration: theme.transitions.duration.standard,
+ }),
+
+ // Prevent content from wrapping or spilling out during animation
+ overflow: 'hidden',
+ whiteSpace: 'nowrap',
+
+ // layout styles
+ height: '100%',
+ display: 'flex',
+ alignItems: 'center',
+ gap: 0.25,
+ pl: show ? 0.25 : 0,
+
+ '&:hover': {
+ cursor: 'pointer',
+ },
+ }}
+ >
+ {/* This inner Box prevents the content from being squeezed during the transition */}
+
+
+
+ {value}
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/features/profile/components/PageWrapper.tsx b/src/features/profile/components/PageWrapper.tsx
new file mode 100644
index 0000000..cbb07c0
--- /dev/null
+++ b/src/features/profile/components/PageWrapper.tsx
@@ -0,0 +1,21 @@
+import { Box } from '@mui/material';
+import React from 'react';
+
+interface PageWrapperProps {
+ children: React.ReactNode;
+}
+
+export function PageWrapper({ children }: PageWrapperProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/features/profile/components/activeDevices/ActiveDevices.tsx b/src/features/profile/components/activeDevices/ActiveDevices.tsx
new file mode 100644
index 0000000..47cce7e
--- /dev/null
+++ b/src/features/profile/components/activeDevices/ActiveDevices.tsx
@@ -0,0 +1,285 @@
+import React, { useState, useEffect } from 'react';
+import {
+ Box,
+ Typography,
+ Button,
+ useTheme,
+ useMediaQuery,
+ CircularProgress,
+} from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { DeviceMessage, Logout } from 'iconsax-react';
+import { CardContainer } from '@/components/CardContainer';
+import { PageWrapper } from '../PageWrapper';
+import { Icon } from '@rkheftan/harmony-ui';
+import { fetchProfile, deleteSessions } from '../../api/settingsApi';
+import { type ApiSession } from '../../types/settingsApiType';
+import { useManualApi } from '@/hooks/useApi';
+import { formatDate } from '@/utils/formatSessionDate';
+import { type Device } from '../../types/settingsType';
+
+export function ActiveDevices() {
+ const { t, i18n } = useTranslation('setting');
+ const [devices, setDevices] = useState([]);
+ const [loadingDeleteIds, setLoadingDeleteIds] = useState([]);
+ const theme = useTheme();
+ const isXsup = useMediaQuery(theme.breakpoints.up('xs'));
+
+ const {
+ data: profileData,
+ loading: isLoading,
+ error: fetchError,
+ execute: executeFetchProfile,
+ } = useManualApi(fetchProfile);
+
+ const {
+ data: terminateData,
+ loading: isTerminating,
+ execute: executeTerminateAll,
+ } = useManualApi(deleteSessions);
+
+ useEffect(() => {
+ executeFetchProfile();
+ }, [i18n.language]);
+
+ useEffect(() => {
+ if (profileData?.success && profileData.activeSessions) {
+ const { sessions, currentKey } = profileData.activeSessions;
+ const formattedDevices = sessions.map((session: ApiSession) => ({
+ id: session.key,
+ timeAndDate: formatDate(session.created, i18n.language, t),
+ deviceModel: `${session.deviceOs} ${session.deviceName}`,
+ ip: session.ipAddress,
+ current: session.key === currentKey,
+ }));
+ setDevices(formattedDevices);
+ }
+ }, [profileData, i18n.language, t]);
+
+ useEffect(() => {
+ if (terminateData?.success) {
+ setDevices((prev) => prev.filter((d) => d.current));
+ }
+ }, [terminateData]);
+
+ const handleDeleteDevice = async (id: string) => {
+ if (loadingDeleteIds.includes(id)) return;
+ setLoadingDeleteIds((prev) => [...prev, id]);
+ try {
+ const { data } = await deleteSessions({ keys: [id] });
+ if (data.success) {
+ setDevices((prevDevices) => prevDevices.filter((d) => d.id !== id));
+ } else {
+ console.error('Delete failed:', data.message);
+ }
+ } catch (error: unknown) {
+ console.error('Delete error:', error);
+ } finally {
+ setLoadingDeleteIds((prev) =>
+ prev.filter((loadingId) => loadingId !== id),
+ );
+ }
+ };
+
+ const handleTerminateAllOtherSessions = () => {
+ const otherSessionKeys = devices.filter((d) => !d.current).map((d) => d.id);
+ if (otherSessionKeys.length > 0) {
+ executeTerminateAll({ keys: otherSessionKeys });
+ }
+ };
+
+ const getErrorMessage = (error: unknown): string | null => {
+ if (!error) return null;
+ if (error instanceof Error) return error.message;
+ return String(error);
+ };
+
+ return (
+
+
+ {isTerminating
+ ? t('active.deleting')
+ : t('active.deleteDevicesButton')}
+
+ }
+ >
+ {isLoading ? (
+
+
+
+ ) : fetchError ? (
+
+
+ {getErrorMessage(fetchError)}
+
+
+ ) : (
+
+ {devices.map((device) => (
+
+
+
+ {device.timeAndDate}
+
+
+
+
+
+ {device.deviceModel}
+
+
+
+
+ {device.ip}
+
+
+
+ {device.current && (
+
+ )}
+
+
+
+ }
+ disabled={
+ device.current || loadingDeleteIds.includes(device.id)
+ }
+ onClick={() => handleDeleteDevice(device.id)}
+ sx={{
+ color: 'error.main',
+ borderRadius: 1,
+ borderColor: 'error.main',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ whiteSpace: 'nowrap',
+ textTransform: 'none',
+ '& .MuiButton-startIcon': {
+ marginRight: 0.5,
+ marginLeft: 0,
+ },
+ }}
+ >
+ {loadingDeleteIds.includes(device.id) ? (
+
+ ) : (
+ t('active.deleteDevice')
+ )}
+
+
+
+ {isXsup && (
+
+ )}
+
+ ))}
+
+ )}
+
+
+ );
+}
diff --git a/src/features/profile/components/security/PasswordDialog.tsx b/src/features/profile/components/security/PasswordDialog.tsx
new file mode 100644
index 0000000..9077c94
--- /dev/null
+++ b/src/features/profile/components/security/PasswordDialog.tsx
@@ -0,0 +1,144 @@
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ TextField,
+ IconButton,
+ Box,
+ Button,
+ Link,
+ CircularProgress,
+ Typography,
+} from '@mui/material';
+import { CloseCircle } from 'iconsax-react';
+import { Icon } from '@rkheftan/harmony-ui';
+import { type PasswordDialogProps } from '../../types/settingsType';
+
+export function PasswordDialog({
+ open,
+ fullScreen,
+ handleClose,
+ password,
+ setPassword,
+ confirmPassword,
+ setConfirmPassword,
+ currentPassword,
+ setCurrentPassword,
+ showValidation,
+ validPassword,
+ matchPassword,
+ loading,
+ handleSubmit,
+ changePassword,
+ apiError,
+ t,
+}: PasswordDialogProps) {
+ return (
+
+ );
+}
diff --git a/src/features/profile/components/security/PasswordSecurity.tsx b/src/features/profile/components/security/PasswordSecurity.tsx
new file mode 100644
index 0000000..a847ee9
--- /dev/null
+++ b/src/features/profile/components/security/PasswordSecurity.tsx
@@ -0,0 +1,191 @@
+import { useState, useEffect, useMemo } from 'react';
+import {
+ Box,
+ Typography,
+ Button,
+ CircularProgress,
+ useTheme,
+ useMediaQuery,
+} from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { CardContainer } from '@/components/CardContainer';
+import { PageWrapper } from '../PageWrapper';
+import { PasswordDialog } from './PasswordDialog';
+import { regex } from '@/utils/regex';
+import { useManualApi } from '@/hooks/useApi';
+import {
+ addPassword,
+ changePassword,
+ fetchProfile,
+} from '../../api/settingsApi';
+
+export function PasswordSecurity() {
+ const theme = useTheme();
+ const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
+ const { t } = useTranslation('setting');
+
+ const [open, setOpen] = useState(false);
+ const [password, setPassword] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+ const [currentPassword, setCurrentPassword] = useState('');
+ const [showValidation, setShowValidation] = useState(false);
+ const [showPasswordAlert, setShowPasswordAlert] = useState(false);
+ const [changePasswordUI, setChangePasswordUI] = useState(false);
+
+ const { validPassword } = regex(password);
+ const matchPassword = password === confirmPassword;
+
+ const {
+ data: profileData,
+ loading: isFetching,
+ error: fetchError,
+ execute: executeFetchProfile,
+ } = useManualApi(fetchProfile);
+
+ const {
+ data: addData,
+ loading: isAdding,
+ error: addError,
+ execute: executeAddPassword,
+ } = useManualApi(addPassword);
+
+ const {
+ data: changeData,
+ loading: isChanging,
+ error: changeError,
+ execute: executeChangePassword,
+ } = useManualApi(changePassword);
+
+ useEffect(() => {
+ executeFetchProfile();
+ }, []);
+
+ useEffect(() => {
+ if (profileData?.success) {
+ setChangePasswordUI(profileData.hasPassword || false);
+ }
+ }, [profileData]);
+
+ useEffect(() => {
+ if (password) {
+ setShowValidation(!validPassword);
+ } else {
+ setShowValidation(false);
+ }
+ }, [password, validPassword]);
+
+ useEffect(() => {
+ if (addData?.success || changeData?.success) {
+ setShowPasswordAlert(true);
+ if (!changePasswordUI) {
+ setChangePasswordUI(true);
+ }
+ setOpen(false);
+ setPassword('');
+ setConfirmPassword('');
+ setCurrentPassword('');
+ }
+ }, [addData, changeData, changePasswordUI]);
+
+ const handleOpen = () => setOpen(true);
+ const handleClose = () => setOpen(false);
+
+ const handlePasswordSubmit = () => {
+ if (changePasswordUI) {
+ executeChangePassword({
+ oldPassword: currentPassword,
+ newPassword: password,
+ });
+ } else {
+ executeAddPassword({ password });
+ }
+ };
+
+ const getErrorMessage = (error: unknown): string | null => {
+ if (!error) return null;
+ if (error instanceof Error) return error.message;
+ return String(error);
+ };
+
+ const apiError = useMemo(
+ () => addError || changeError,
+ [addError, changeError],
+ );
+
+ return (
+
+
+ {changePasswordUI
+ ? t('securityForm.changePassword')
+ : t('securityForm.addPassword')}
+
+ }
+ >
+ {isFetching ? (
+
+
+
+ ) : fetchError ? (
+
+
+ {getErrorMessage(fetchError)}
+
+
+ ) : (
+
+ {changePasswordUI ? (
+
+
+ {t('securityForm.activePassword')}
+
+
+ {t('securityForm.lastChange')}
+
+
+ ) : (
+
+ {t('securityForm.notDeterminedPassword')}
+
+ )}
+
+
+
+ )}
+
+
+ );
+}
diff --git a/src/features/profile/components/security/PasswordValidation.tsx b/src/features/profile/components/security/PasswordValidation.tsx
new file mode 100644
index 0000000..29b9d94
--- /dev/null
+++ b/src/features/profile/components/security/PasswordValidation.tsx
@@ -0,0 +1,39 @@
+import { Box, Typography } from '@mui/material';
+import { TickCircle } from 'iconsax-react';
+import { Icon } from '@rkheftan/harmony-ui';
+import { type ValidationItemProps } from '../../types/settingsType';
+
+export function PasswordValidationItem({
+ isValid,
+ label,
+}: ValidationItemProps) {
+ return (
+
+
+
+ {label}
+
+
+ );
+}
diff --git a/src/features/profile/components/security/RecentLogins.tsx b/src/features/profile/components/security/RecentLogins.tsx
new file mode 100644
index 0000000..a285229
--- /dev/null
+++ b/src/features/profile/components/security/RecentLogins.tsx
@@ -0,0 +1,105 @@
+import React, { useState, useEffect } from 'react';
+import {
+ Box,
+ Typography,
+ CircularProgress,
+ useTheme,
+ useMediaQuery,
+} from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { CardContainer } from '@/components/CardContainer';
+import { PageWrapper } from '../PageWrapper';
+import { fetchProfile } from '../../api/settingsApi';
+import type { LoginLog } from '../../types/settingsApiType';
+import { useManualApi } from '@/hooks/useApi';
+import { formatDate } from '@/utils/formatSessionDate';
+
+export function RecentLogins() {
+ const { t, i18n } = useTranslation('setting');
+ const [logs, setLogs] = useState([]);
+ const theme = useTheme();
+ const isXsup = useMediaQuery(theme.breakpoints.up('xs'));
+
+ const {
+ data: profileData,
+ loading: isLoading,
+ error: fetchError,
+ execute: executeFetchProfile,
+ } = useManualApi(fetchProfile);
+
+ useEffect(() => {
+ executeFetchProfile();
+ }, [i18n.language]);
+
+ useEffect(() => {
+ if (profileData?.success && Array.isArray(profileData.loginLogs)) {
+ setLogs(profileData.loginLogs);
+ }
+ }, [profileData]);
+
+ const getErrorMessage = (error: unknown): string | null => {
+ if (!error) return null;
+ if (error instanceof Error) return error.message;
+ return String(error);
+ };
+
+ return (
+
+
+ {isLoading ? (
+
+
+
+ ) : fetchError ? (
+
+
+ {getErrorMessage(fetchError) || t('active.errorFetch')}
+
+
+ ) : (
+
+ {logs.map((log, index) => (
+
+
+
+ {formatDate(log.loginDateTime, i18n.language, t)}
+
+
+ {`${log.deviceOs} ${log.deviceName}`}
+
+
+ {log.ipAddress}
+
+
+ {isXsup && index < logs.length - 1 && (
+
+ )}
+
+ ))}
+
+ )}
+
+
+ );
+}
diff --git a/src/features/profile/components/setting/Setting.tsx b/src/features/profile/components/setting/Setting.tsx
new file mode 100644
index 0000000..1037286
--- /dev/null
+++ b/src/features/profile/components/setting/Setting.tsx
@@ -0,0 +1,316 @@
+import { useState, useEffect } from 'react';
+import {
+ Box,
+ Typography,
+ Button,
+ useColorScheme,
+ Autocomplete,
+ TextField,
+ CircularProgress,
+} from '@mui/material';
+import { CardContainer } from '@/components/CardContainer';
+import { useTranslation } from 'react-i18next';
+import { ThemeToggleButton } from '@/components/ThemToggle';
+import { PageWrapper } from '../PageWrapper';
+import { Icon } from '@rkheftan/harmony-ui';
+import { Sun1, Moon, Calendar1 } from 'iconsax-react';
+import { useManualApi } from '@/hooks/useApi';
+import { fetchProfile, saveSettings } from '../../api/settingsApi';
+
+type ThemeMode = 'light' | 'dark';
+type CalendarType = 'christian' | 'solar' | 'lunar';
+
+interface SettingsState {
+ language: string;
+ calendar: CalendarType;
+ theme: ThemeMode;
+}
+
+const languageOptions = [
+ { code: 'en', label: 'English', apiValue: 1 },
+ { code: 'fa', label: 'فارسی', apiValue: 2 },
+];
+
+const calendarOptions: { key: CalendarType; apiValue: number }[] = [
+ { key: 'christian', apiValue: 1 },
+ { key: 'solar', apiValue: 2 },
+ { key: 'lunar', apiValue: 3 },
+];
+
+const themeApiMap: Record = { light: 1, dark: 2 };
+
+export function Setting() {
+ const { t, i18n } = useTranslation(['setting']);
+ const { mode, setMode } = useColorScheme();
+
+ const [savedSettings, setSavedSettings] = useState({
+ language: i18n.language,
+ calendar: 'solar',
+ theme: mode === 'light' || mode === 'dark' ? mode : 'light',
+ });
+ const [draftSettings, setDraftSettings] =
+ useState(savedSettings);
+ const [isEditing, setIsEditing] = useState(false);
+
+ const {
+ data: profileData,
+ loading: isFetching,
+ error: fetchError,
+ execute: executeFetchProfile,
+ } = useManualApi(fetchProfile);
+
+ const {
+ data: saveData,
+ loading: isSaving,
+ error: saveError,
+ execute: executeSaveSettings,
+ } = useManualApi(saveSettings);
+
+ useEffect(() => {
+ executeFetchProfile();
+ }, []);
+
+ useEffect(() => {
+ if (profileData?.success && profileData.userSettings) {
+ const { theme, calendarType, language } = profileData.userSettings;
+ const themeReverseMap: { [key: number]: ThemeMode | undefined } = {
+ 1: 'light',
+ 2: 'dark',
+ };
+ const newSettings: SettingsState = {
+ theme: themeReverseMap[theme] || 'light',
+ calendar:
+ calendarOptions.find((c) => c.apiValue === calendarType)?.key ||
+ 'solar',
+ language:
+ languageOptions.find((l) => l.apiValue === language)?.code || 'en',
+ };
+ setSavedSettings(newSettings);
+ setDraftSettings(newSettings);
+ setMode(newSettings.theme);
+ i18n.changeLanguage(newSettings.language);
+ }
+ }, [profileData, setMode, i18n]);
+
+ useEffect(() => {
+ if (saveData?.success) {
+ setMode(draftSettings.theme);
+ setSavedSettings(draftSettings);
+ i18n.changeLanguage(draftSettings.language);
+ setIsEditing(false);
+ }
+ }, [saveData, draftSettings, setMode, i18n]);
+
+ useEffect(() => {
+ if (isEditing) {
+ const resolvedMode = mode === 'light' || mode === 'dark' ? mode : 'light';
+ setDraftSettings({ ...savedSettings, theme: resolvedMode });
+ }
+ }, [isEditing, savedSettings, mode]);
+
+ const handleCancel = () => {
+ setIsEditing(false);
+ setDraftSettings(savedSettings);
+ setMode(savedSettings.theme);
+ };
+
+ const handleEditToggle = () => {
+ if (isEditing) {
+ const languageObj = languageOptions.find(
+ (o) => o.code === draftSettings.language,
+ );
+ const calendarObj = calendarOptions.find(
+ (c) => c.key === draftSettings.calendar,
+ );
+
+ if (languageObj && calendarObj) {
+ executeSaveSettings({
+ theme: themeApiMap[draftSettings.theme],
+ calendarType: calendarObj.apiValue,
+ language: languageObj.apiValue,
+ });
+ }
+ } else {
+ setIsEditing(true);
+ }
+ };
+ const getErrorMessage = (error: unknown): string | null => {
+ if (!error) return null;
+ if (error instanceof Error) return error.message;
+ return String(error);
+ };
+
+ return (
+
+
+ {isEditing && (
+
+ )}
+
+
+ }
+ >
+ {isFetching ? (
+
+
+
+ ) : fetchError ? (
+
+
+ {getErrorMessage(fetchError)}
+
+
+ ) : (
+
+ {getErrorMessage(saveError) && (
+
+ {getErrorMessage(saveError)}
+
+ )}
+
+
+
+ {t('settings.theme')}
+
+ {isEditing ? (
+ {
+ setDraftSettings((prev) => ({
+ ...prev,
+ theme: newTheme,
+ }));
+ }}
+ />
+ ) : (
+
+
+
+ {t(`settings.${savedSettings.theme}`)}
+
+
+ )}
+
+
+
+ {t('settings.language')}
+
+ {isEditing ? (
+ o.label}
+ value={
+ languageOptions.find(
+ (o) => o.code === draftSettings.language,
+ ) // ✅ FIX: Removed '|| null'
+ }
+ onChange={(_, v) =>
+ v &&
+ setDraftSettings((prev) => ({
+ ...prev,
+ language: v.code,
+ }))
+ }
+ renderInput={(p) => }
+ size="small"
+ fullWidth
+ disableClearable
+ />
+ ) : (
+
+ {
+ languageOptions.find(
+ (o) => o.code === savedSettings.language,
+ )?.label
+ }
+
+ )}
+
+
+
+
+ {t('settings.calendar')}
+
+ {isEditing ? (
+ c.key)}
+ getOptionLabel={(key) => t(`settings.${key}`)}
+ value={draftSettings.calendar}
+ onChange={(_, v) =>
+ v && setDraftSettings((prev) => ({ ...prev, calendar: v }))
+ }
+ renderInput={(params) => }
+ size="small"
+ fullWidth
+ disableClearable
+ />
+ ) : (
+
+
+
+ {t(`settings.${savedSettings.calendar}`)}
+
+
+ )}
+
+
+ )}
+
+
+ );
+}
diff --git a/src/features/profile/components/userInformation/PersonalInformation.tsx b/src/features/profile/components/userInformation/PersonalInformation.tsx
new file mode 100644
index 0000000..d89b4c1
--- /dev/null
+++ b/src/features/profile/components/userInformation/PersonalInformation.tsx
@@ -0,0 +1,230 @@
+import { useState, useEffect } from 'react';
+import { Box, Button, Typography, CircularProgress } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { CardContainer } from '@/components/CardContainer';
+import { ProfileImage } from './personalInformation/ProfileImage';
+import { InfoRowDisplay } from './personalInformation/InfoRowDisplay';
+import { InfoRowEdit } from './personalInformation/InfoRowEdit';
+import { PageWrapper } from '../PageWrapper';
+import { useManualApi } from '@/hooks/useApi';
+import { Gender, type InfoRowData } from '../../types/settingsType';
+import { fetchProfile, saveProfile } from '../../api/settingsApi';
+
+export function PersonalInformation() {
+ const { t } = useTranslation('setting');
+ const [isEditing, setIsEditing] = useState(false);
+ const [uploadedImageUrl, setUploadedImageUrl] = useState(null);
+ const [data, setData] = useState({
+ firstName: '',
+ lastName: '',
+ nationalCode: '',
+ gender: Gender.None,
+ country: '',
+ });
+ const [originalData, setOriginalData] = useState(null);
+
+ const {
+ data: profileData,
+ loading: isLoadingProfile,
+ error: fetchProfileError,
+ execute: executeFetchProfile,
+ } = useManualApi(fetchProfile);
+
+ const {
+ data: saveData,
+ loading: isSavingProfile,
+ error: saveProfileError,
+ execute: executeSaveProfile,
+ } = useManualApi(saveProfile);
+
+ useEffect(() => {
+ executeFetchProfile();
+ }, []);
+
+ useEffect(() => {
+ if (profileData?.success) {
+ const fetchedData = {
+ firstName: profileData.firstName ?? '',
+ lastName: profileData.lastName ?? '',
+ nationalCode: profileData.nationalCode ?? '',
+ gender: Object.values(Gender).includes(profileData.gender as Gender)
+ ? (profileData.gender as Gender)
+ : Gender.None,
+ country: profileData.countryCode ?? '',
+ };
+ setData(fetchedData);
+ setOriginalData(fetchedData);
+ setUploadedImageUrl(profileData.profileImageUrl || null);
+ }
+ }, [profileData]);
+
+ useEffect(() => {
+ if (saveData?.success) {
+ setIsEditing(false);
+ setOriginalData(data);
+ }
+ }, [saveData, data]);
+
+ const initials = `${data?.firstName?.trim()[0] || ''}${data?.lastName?.trim()[0] || ''}`;
+
+ const handleEditClick = () => {
+ setIsEditing(true);
+ setOriginalData(data);
+ };
+
+ const handleCancelClick = () => {
+ setIsEditing(false);
+ if (originalData) {
+ setData(originalData);
+ }
+ };
+
+ const handleSaveClick = async () => {
+ if (!data) return;
+ executeSaveProfile({ data, imageUrl: uploadedImageUrl });
+ };
+
+ const getErrorMessage = (error: unknown): string | null => {
+ if (!error) return null;
+ if (error instanceof Error) return error.message;
+ return String(error);
+ };
+
+ return (
+
+
+
+ {isEditing ? (
+ <>
+
+
+ >
+ ) : (
+
+ )}
+
+ {getErrorMessage(saveProfileError) && (
+
+ {getErrorMessage(saveProfileError)}
+
+ )}
+
+ }
+ >
+ {isLoadingProfile ? (
+
+
+
+ ) : fetchProfileError ? (
+
+
+ {getErrorMessage(fetchProfileError) ||
+ t('settingForm.errorFetch')}
+
+
+ ) : (
+ <>
+
+ {isEditing && (
+ {
+ const reader = new FileReader();
+ reader.onload = () =>
+ setUploadedImageUrl(reader.result as string);
+ reader.readAsDataURL(file);
+ }}
+ onRemoveImage={() => setUploadedImageUrl(null)}
+ />
+ )}
+ {data &&
+ (isEditing ? (
+
+ ) : (
+
+ ))}
+
+ >
+ )}
+
+
+ );
+}
diff --git a/src/features/profile/components/userInformation/PhoneNumber.tsx b/src/features/profile/components/userInformation/PhoneNumber.tsx
new file mode 100644
index 0000000..148e937
--- /dev/null
+++ b/src/features/profile/components/userInformation/PhoneNumber.tsx
@@ -0,0 +1,256 @@
+import { useState, useRef, useEffect, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import parsePhoneNumberFromString from 'libphonenumber-js';
+import { PageWrapper } from '../PageWrapper';
+import { CardContainer } from '@/components/CardContainer';
+import PhoneDisplay from './phoneNumber/PhoneDisplay';
+import PhoneEditForm from './phoneNumber/PhoneEditForm';
+import PhoneActionButtons from './phoneNumber/PhoneActionButtons';
+import { CircularProgress, Box, Typography } from '@mui/material';
+import { toLocaleDigits } from '@/utils/persianDigit';
+import { useManualApi } from '@/hooks/useApi';
+import {
+ fetchProfile,
+ sendVerificationCode,
+ confirmPhoneNumberCode,
+ changePhoneNumber,
+} from '../../api/settingsApi';
+import { type Phone } from '../../types/settingsType';
+
+export function PhoneNumber() {
+ const { t, i18n } = useTranslation('setting');
+ const [isEditing, setIsEditing] = useState(false);
+ const [phoneNumber, setPhoneNumber] = useState('');
+ const [verificationCode, setVerificationCode] = useState('');
+ const [showToast, setShowToast] = useState(false);
+ const [buttonState, setButtonState] = useState<'default' | 'counting'>(
+ 'default',
+ );
+ const [isVerified, setIsVerified] = useState(false);
+ const [phones, setPhones] = useState([]);
+ const [countryCode, setCountryCode] = useState('+98');
+ const [formError, setFormError] = useState();
+ const [touched, setTouched] = useState(false);
+
+ const textFieldRef = useRef(null);
+ const inputRef = useRef(null);
+
+ const {
+ data: profileData,
+ loading: isLoading,
+ error: fetchError,
+ execute: executeFetchProfile,
+ } = useManualApi(fetchProfile);
+
+ const {
+ data: sendCodeData,
+ loading: isSendingCode,
+ error: sendCodeError,
+ execute: executeSendCode,
+ } = useManualApi(sendVerificationCode);
+
+ const {
+ data: confirmData,
+ loading: isVerifying,
+ error: confirmError,
+ execute: executeConfirmCode,
+ } = useManualApi(confirmPhoneNumberCode);
+
+ const {
+ data: changePhoneData,
+ loading: isChangingPhone,
+ error: changePhoneError,
+ execute: executeChangePhone,
+ } = useManualApi(changePhoneNumber);
+
+ useEffect(() => {
+ if (!isEditing) {
+ executeFetchProfile();
+ }
+ }, [isEditing]);
+
+ useEffect(() => {
+ if (profileData?.success && profileData.phoneNumber) {
+ setPhones([
+ {
+ phone: profileData.phoneNumber,
+ time: '',
+ withCode: profileData.phoneNumber,
+ },
+ ]);
+ }
+ }, [profileData]);
+
+ useEffect(() => {
+ if (sendCodeData?.success) {
+ setButtonState('counting');
+ setIsVerified(false);
+ }
+ }, [sendCodeData]);
+
+ useEffect(() => {
+ if (confirmData?.success && confirmData.confirm) {
+ setIsVerified(true);
+ setShowToast(true);
+ const fullPhoneNumber = countryCode + phoneNumber.replace(/^0/, '');
+ executeChangePhone({ phoneNumber: fullPhoneNumber });
+ }
+ }, [confirmData, countryCode, phoneNumber, executeChangePhone]);
+
+ useEffect(() => {
+ if (changePhoneData?.success) {
+ const fullPhoneNumber = countryCode + phoneNumber.replace(/^0/, '');
+ setPhones([
+ {
+ phone: phoneNumber,
+ time: t('settingForm.justNow'),
+ withCode: fullPhoneNumber,
+ },
+ ]);
+ setIsEditing(false);
+ }
+ }, [changePhoneData, countryCode, phoneNumber, t]);
+
+ const apiError = useMemo(
+ () => sendCodeError || confirmError || changePhoneError,
+ [sendCodeError, confirmError, changePhoneError],
+ );
+
+ const getErrorMessage = (error: unknown): string | undefined => {
+ if (!error) return undefined;
+ if (error instanceof Error) return error.message;
+ return String(error);
+ };
+
+ const isPhoneValid = (code: string, phone: string) => {
+ const phoneNum = parsePhoneNumberFromString(code + phone);
+ return phoneNum?.isValid();
+ };
+
+ const handleBlur = () => {
+ setTouched(true);
+ if (!phoneNumber) {
+ setFormError(t('settingForm.thisFieldIsRequired'));
+ return;
+ }
+ if (!isPhoneValid(countryCode, phoneNumber)) {
+ setFormError(t('settingForm.phoneNumberIsInvalid'));
+ } else {
+ setFormError(undefined);
+ }
+ };
+
+ const toggleEdit = () => {
+ setIsEditing((prev) => {
+ if (!prev) {
+ setPhoneNumber('');
+ setVerificationCode('');
+ setIsVerified(false);
+ setButtonState('default');
+ setShowToast(false);
+ setFormError(undefined);
+ setTouched(false);
+ }
+ return !prev;
+ });
+ };
+
+ const handleSendCode = () => {
+ handleBlur();
+ if (formError || !isPhoneValid(countryCode, phoneNumber)) return;
+
+ executeSendCode({
+ phoneNumber: countryCode + phoneNumber.replace(/^0/, ''),
+ });
+ };
+
+ const handleVerifyCode = () => {
+ if (!verificationCode) {
+ setFormError(t('settingForm.verificationCodeRequired'));
+ return;
+ }
+ setFormError(undefined);
+ executeConfirmCode({
+ phoneNumber: countryCode + phoneNumber.replace(/^0/, ''),
+ verifyCode: verificationCode,
+ });
+ };
+
+ const combinedError = formError || getErrorMessage(apiError);
+ const inputError: boolean = touched && !!combinedError;
+
+ return (
+
+
+ }
+ >
+ {isLoading ? (
+
+
+
+ ) : fetchError ? (
+
+
+ {getErrorMessage(fetchError) ||
+ t('settingForm.errorFetchPhoneNumber')}
+
+
+ ) : isEditing ? (
+ }
+ inputRef={inputRef as React.RefObject}
+ phones={phones}
+ showToast={showToast}
+ setShowToast={setShowToast}
+ t={t}
+ />
+ ) : (
+ {
+ let localPhone = p.withCode;
+ if (localPhone.startsWith('+98')) {
+ localPhone = '0' + localPhone.slice(3);
+ }
+ return {
+ ...p,
+ phone: toLocaleDigits(localPhone, i18n.language),
+ };
+ })}
+ />
+ )}
+
+
+ );
+}
diff --git a/src/features/profile/components/userInformation/SocialMedia.tsx b/src/features/profile/components/userInformation/SocialMedia.tsx
new file mode 100644
index 0000000..98dc1ab
--- /dev/null
+++ b/src/features/profile/components/userInformation/SocialMedia.tsx
@@ -0,0 +1,200 @@
+import { useState, useEffect, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { CardContainer } from '@/components/CardContainer';
+import { PageWrapper } from '../PageWrapper';
+import SocialMediaList from './socialMedia/SocialMediaList';
+import SocialMediaMenu from './socialMedia/SocialMediaMenu';
+import SocialMediaDialog from './socialMedia/SocialMediaDialog';
+import useMediaQuery from '@mui/material/useMediaQuery';
+import type { Theme } from '@mui/material/styles';
+import { Box, CircularProgress, Typography } from '@mui/material';
+import { useManualApi } from '@/hooks/useApi';
+import {
+ fetchProfile,
+ sendEmailCode,
+ confirmEmailCode,
+ changeEmail,
+} from '../../api/settingsApi';
+import { type EmailAccount } from '../../types/settingsType';
+
+export function SocialMedia() {
+ const { t } = useTranslation('setting');
+
+ const [openDialog, setOpenDialog] = useState(false);
+ const [emailInput, setEmailInput] = useState('');
+ const [verificationCode, setVerificationCode] = useState('');
+ const [dialogStep, setDialogStep] = useState<'enterEmail' | 'enterCode'>(
+ 'enterEmail',
+ );
+ const [emailList, setEmailList] = useState([]);
+ const [formError, setFormError] = useState(null);
+
+ const {
+ data: profileData,
+ loading: isFetching,
+ error: fetchError,
+ execute: executeFetchProfile,
+ } = useManualApi(fetchProfile);
+
+ const {
+ data: sendCodeData,
+ loading: isSendingCode,
+ error: sendCodeError,
+ execute: executeSendCode,
+ } = useManualApi(sendEmailCode);
+
+ const {
+ data: confirmData,
+ loading: isConfirming,
+ error: confirmError,
+ execute: executeConfirmCode,
+ } = useManualApi(confirmEmailCode);
+
+ const {
+ data: changeEmailData,
+ loading: isChangingEmail,
+ error: changeEmailError,
+ execute: executeChangeEmail,
+ } = useManualApi(changeEmail);
+
+ const fullScreen = useMediaQuery((theme: Theme) =>
+ theme.breakpoints.down('sm'),
+ );
+ const downMd = useMediaQuery((theme: Theme) => theme.breakpoints.down('md'));
+ const computedMaxWidth = (fullScreen ? 'xs' : downMd ? 'sm' : 'md') as
+ | 'xs'
+ | 'sm'
+ | 'md'
+ | 'lg'
+ | 'xl';
+
+ useEffect(() => {
+ executeFetchProfile();
+ }, []);
+
+ useEffect(() => {
+ if (profileData?.success && profileData.email) {
+ const { email } = profileData;
+ setEmailList([
+ {
+ email,
+ provider: email.includes('@gmail.') ? 'google' : 'email',
+ time: '',
+ },
+ ]);
+ }
+ }, [profileData]);
+
+ useEffect(() => {
+ if (sendCodeData?.success) {
+ setDialogStep('enterCode');
+ }
+ }, [sendCodeData]);
+
+ useEffect(() => {
+ if (confirmData?.success && confirmData.confirm) {
+ executeChangeEmail({ email: emailInput });
+ }
+ }, [confirmData, emailInput, executeChangeEmail]);
+
+ useEffect(() => {
+ if (changeEmailData?.success) {
+ setEmailList((prev) => [
+ ...prev,
+ {
+ email: emailInput,
+ provider: emailInput.includes('@gmail.') ? 'google' : 'email',
+ time: t('settingForm.justNow'),
+ },
+ ]);
+ resetDialog();
+ }
+ }, [changeEmailData, emailInput, t]);
+
+ const resetDialog = () => {
+ setOpenDialog(false);
+ setEmailInput('');
+ setVerificationCode('');
+ setFormError(null);
+ setDialogStep('enterEmail');
+ };
+
+ const handleSendCode = () => {
+ if (!/^\S+@\S+\.\S+$/.test(emailInput)) {
+ setFormError(t('settingForm.emailIsInvalid'));
+ return;
+ }
+ setFormError(null);
+ executeSendCode({ email: emailInput });
+ };
+
+ const handleConfirmAndChangeEmail = () => {
+ if (verificationCode.length < 4) {
+ setFormError(t('settingForm.verificationCodeRequired'));
+ return;
+ }
+ setFormError(null);
+ executeConfirmCode({ email: emailInput, verifyCode: verificationCode });
+ };
+
+ const getErrorMessage = (error: unknown): string | null => {
+ if (!error) return null;
+ if (error instanceof Error) return error.message;
+ return String(error);
+ };
+
+ const apiError = useMemo(
+ () => getErrorMessage(sendCodeError || confirmError || changeEmailError),
+ [sendCodeError, confirmError, changeEmailError],
+ );
+
+ return (
+
+ setOpenDialog(true)} />
+ }
+ >
+ {isFetching ? (
+
+
+
+ ) : fetchError ? (
+
+
+ {getErrorMessage(fetchError)}
+
+
+ ) : (
+
+ )}
+
+
+
+ );
+}
diff --git a/src/features/profile/components/userInformation/personalInformation/DisplayField.tsx b/src/features/profile/components/userInformation/personalInformation/DisplayField.tsx
new file mode 100644
index 0000000..4dd1ee3
--- /dev/null
+++ b/src/features/profile/components/userInformation/personalInformation/DisplayField.tsx
@@ -0,0 +1,23 @@
+import { Box, Typography } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { type DisplayFieldProps } from '@/features/profile/types/settingsType';
+
+export function DisplayField({ label, value }: DisplayFieldProps) {
+ const { t } = useTranslation('setting');
+ const displayValue = value?.trim() || t('settingForm.notDetermined');
+
+ return (
+
+
+ {label}
+
+
+ {displayValue}
+
+
+ );
+}
diff --git a/src/features/profile/components/userInformation/personalInformation/InfoRowDisplay.tsx b/src/features/profile/components/userInformation/personalInformation/InfoRowDisplay.tsx
new file mode 100644
index 0000000..9f95ceb
--- /dev/null
+++ b/src/features/profile/components/userInformation/personalInformation/InfoRowDisplay.tsx
@@ -0,0 +1,105 @@
+import { Box, Typography, Avatar } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { DisplayField } from './DisplayField';
+import { CountryFlag } from '@/components/CountryFlag';
+import { Gender } from '@/features/profile/types/settingsType';
+import { type InfoRowDisplayProps } from '@/features/profile/types/settingsType';
+
+export function InfoRowDisplay({
+ data,
+ uploadedImageUrl,
+ initials,
+}: InfoRowDisplayProps) {
+ const { t } = useTranslation('setting');
+ const displayValue = (value: string) =>
+ value?.trim() || t('settingForm.notDetermined');
+
+ const getGenderLabel = (gender: Gender | '') => {
+ switch (gender) {
+ case Gender.Male:
+ return t('settingForm.man');
+ case Gender.Female:
+ return t('settingForm.woman');
+ default:
+ return t('settingForm.notDetermined');
+ }
+ };
+
+ return (
+
+
+
+
+
+ {t('settingForm.name')} و {t('settingForm.familyName')}
+
+
+
+ {initials}
+
+
+ {`${displayValue(data.firstName)} ${displayValue(
+ data.lastName,
+ )}`}
+
+
+
+
+
+
+ {t('settingForm.country')}
+
+
+ {data.country ? (
+
+ ) : (
+
+ {t('settingForm.notDetermined')}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/features/profile/components/userInformation/personalInformation/InfoRowEdit.tsx b/src/features/profile/components/userInformation/personalInformation/InfoRowEdit.tsx
new file mode 100644
index 0000000..3c7f9c6
--- /dev/null
+++ b/src/features/profile/components/userInformation/personalInformation/InfoRowEdit.tsx
@@ -0,0 +1,118 @@
+import {
+ Box,
+ TextField,
+ FormControl,
+ InputLabel,
+ MenuItem,
+ Select,
+ Autocomplete,
+} from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { countries } from '@/features/profile/data/countries';
+import { CountryFlag } from '@/components/CountryFlag';
+import { Gender } from '@/features/profile/types/settingsType';
+import { type InfoRowEditProps } from '@/features/profile/types/settingsType';
+
+export function InfoRowEdit({ data, setData }: InfoRowEditProps) {
+ const { t } = useTranslation(['countries', 'setting']);
+
+ const countryOptions = countries.map((c) => ({
+ code: c.code,
+ label: t(c.label, { ns: 'countries' }),
+ }));
+
+ const currentCountry =
+ countryOptions.find((c) => c.code === data.country) || null;
+ const fields = [
+ {
+ name: 'firstName' as const,
+ label: t('settingForm.name', { ns: 'setting' }),
+ value: data.firstName,
+ },
+ {
+ name: 'lastName' as const,
+ label: t('settingForm.familyName', { ns: 'setting' }),
+ value: data.lastName,
+ },
+ {
+ name: 'nationalCode' as const,
+ label: t('settingForm.nationalCode', { ns: 'setting' }),
+ value: data.nationalCode,
+ },
+ ];
+
+ return (
+
+ {fields.map(({ name, label, value }) => (
+
+
+ setData((prev) => ({
+ ...prev,
+ [name]: e.target.value,
+ }))
+ }
+ label={label}
+ />
+
+ ))}
+
+
+
+
+ {t('settingForm.genderPlaceholder', { ns: 'setting' })}
+
+
+
+
+
+ option.label}
+ value={currentCountry}
+ onChange={(_, newValue) =>
+ setData((prev) => ({
+ ...prev,
+ country: newValue?.code || '',
+ }))
+ }
+ renderOption={(props, option) => (
+
+
+ {option.label}
+
+ )}
+ renderInput={(params) => (
+
+ )}
+ clearOnEscape
+ />
+
+ );
+}
diff --git a/src/features/profile/components/userInformation/personalInformation/ProfileImage.tsx b/src/features/profile/components/userInformation/personalInformation/ProfileImage.tsx
new file mode 100644
index 0000000..4baa6ad
--- /dev/null
+++ b/src/features/profile/components/userInformation/personalInformation/ProfileImage.tsx
@@ -0,0 +1,108 @@
+import React, { useState } from 'react';
+import { Box, Avatar, Typography, Button, IconButton } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { Camera, Trash } from 'iconsax-react';
+import { Icon } from '@rkheftan/harmony-ui';
+import { type ProfileImageProps } from '@/features/profile/types/settingsType';
+
+const MAX_FILE_SIZE_MB = 10;
+
+export function ProfileImage({
+ initials,
+ uploadedImageUrl,
+ onImageChange,
+ onRemoveImage,
+}: ProfileImageProps) {
+ const { t } = useTranslation('setting');
+ const [error, setError] = useState(null);
+
+ const handleImageChange = (e: React.ChangeEvent) => {
+ setError(null);
+ const file = e.target.files?.[0];
+ if (!file) return;
+
+ const fileSizeMB = file.size / (1024 * 1024);
+ if (fileSizeMB > MAX_FILE_SIZE_MB) {
+ setError(t('settingForm.fileSizeError', { size: MAX_FILE_SIZE_MB }));
+ return;
+ }
+
+ onImageChange(file);
+ };
+
+ return (
+
+
+ {initials}
+
+
+
+ {t('settingForm.profilePicture')}
+
+
+ {t('settingForm.allowedFormat')}
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+
+ }
+ >
+ {uploadedImageUrl && onRemoveImage
+ ? t('settingForm.changePicture')
+ : t('settingForm.uploadPicture')}
+
+
+ {uploadedImageUrl && onRemoveImage && (
+
+
+
+ )}
+
+
+
+ );
+}
diff --git a/src/features/profile/components/userInformation/phoneNumber/PhoneActionButtons.tsx b/src/features/profile/components/userInformation/phoneNumber/PhoneActionButtons.tsx
new file mode 100644
index 0000000..6f523ac
--- /dev/null
+++ b/src/features/profile/components/userInformation/phoneNumber/PhoneActionButtons.tsx
@@ -0,0 +1,43 @@
+import { Box, Button } from '@mui/material';
+import { type PhoneActionButtonsProps } from '@/features/profile/types/settingsType';
+
+export default function PhoneActionButtons({
+ isEditing,
+ toggleEdit,
+ t,
+}: PhoneActionButtonsProps) {
+ return (
+
+ {isEditing && (
+
+ )}
+
+
+ );
+}
diff --git a/src/features/profile/components/userInformation/phoneNumber/PhoneDisplay.tsx b/src/features/profile/components/userInformation/phoneNumber/PhoneDisplay.tsx
new file mode 100644
index 0000000..76f2c0f
--- /dev/null
+++ b/src/features/profile/components/userInformation/phoneNumber/PhoneDisplay.tsx
@@ -0,0 +1,52 @@
+import { Box, Typography } from '@mui/material';
+import { Icon } from '@rkheftan/harmony-ui';
+import { Mobile } from 'iconsax-react';
+import { type PhoneDisplayProps } from '@/features/profile/types/settingsType';
+
+export default function PhoneDisplay({ phones }: PhoneDisplayProps) {
+ return (
+ <>
+ {phones.map((item, index) => (
+
+
+
+
+
+
+
+ {item.phone}
+
+
+ {item.time}
+
+
+
+ ))}
+ >
+ );
+}
diff --git a/src/features/profile/components/userInformation/phoneNumber/PhoneEditForm.tsx b/src/features/profile/components/userInformation/phoneNumber/PhoneEditForm.tsx
new file mode 100644
index 0000000..b900bc9
--- /dev/null
+++ b/src/features/profile/components/userInformation/phoneNumber/PhoneEditForm.tsx
@@ -0,0 +1,196 @@
+import {
+ Box,
+ Typography,
+ TextField,
+ Button,
+ IconButton,
+ InputAdornment,
+ CircularProgress,
+} from '@mui/material';
+import { Edit2, TickCircle } from 'iconsax-react';
+import { CountDownTimer } from '@/components/CountDownTimer';
+import { CountryCodeSelector } from '../../CountryCodeSelector';
+import { Icon } from '@rkheftan/harmony-ui';
+import { type PhoneEditFormProps } from '@/features/profile/types/settingsType';
+
+export default function PhoneEditForm({
+ phoneNumber,
+ setPhoneNumber,
+ countryCode,
+ setCountryCode,
+ verificationCode,
+ setVerificationCode,
+ isVerified,
+ isVerifying,
+ buttonState,
+ setButtonState,
+ handleSendCode,
+ handleVerifyClick,
+ error,
+ inputError,
+ handleBlur,
+ textFieldRef,
+ inputRef,
+ phones,
+ t,
+}: PhoneEditFormProps) {
+ const isValidPhoneNumber = (phone: string) => {
+ const digitsOnly = phone.replace(/\D/g, '');
+ return digitsOnly.length >= 8 && digitsOnly.length <= 15;
+ };
+
+ return (
+ <>
+
+
+
+ {t('settingForm.editPhoneNumber')}
+
+
+ {t('settingForm.phoneNumberText')}({phones.map((p) => p.withCode)})
+ {t('settingForm.verb')}
+
+
+
+
+
+ setPhoneNumber(e.target.value)}
+ sx={{ flex: '1 1 220px', minWidth: 0 }}
+ placeholder="09123456789"
+ InputProps={{
+ endAdornment:
+ buttonState === 'counting' ? (
+
+ {
+ setButtonState('default');
+ setPhoneNumber('');
+ setVerificationCode('');
+ }}
+ edge="end"
+ >
+
+
+
+ ) : (
+
+ ),
+ }}
+ />
+
+ {isVerified ? (
+
+
+ {t('settingForm.successButton')}
+
+ ) : (
+
+ )}
+
+
+ {buttonState === 'counting' && !isVerified && (
+
+
+ setVerificationCode(e.target.value.replace(/\D/g, ''))
+ }
+ sx={{ flex: '1 1 240px', minWidth: 0 }}
+ placeholder={t('settingForm.verificationCode')}
+ />
+
+
+
+ )}
+ >
+ );
+}
diff --git a/src/features/profile/components/userInformation/socialMedia/SocialMediaDialog.tsx b/src/features/profile/components/userInformation/socialMedia/SocialMediaDialog.tsx
new file mode 100644
index 0000000..7d7b48a
--- /dev/null
+++ b/src/features/profile/components/userInformation/socialMedia/SocialMediaDialog.tsx
@@ -0,0 +1,148 @@
+import React, { type ReactElement, type ElementType } from 'react';
+import {
+ Box,
+ Button,
+ CircularProgress,
+ Dialog,
+ DialogContent,
+ DialogTitle,
+ IconButton,
+ TextField,
+ Typography,
+} from '@mui/material';
+import Slide from '@mui/material/Slide';
+import type { TransitionProps } from '@mui/material/transitions';
+import { CloseCircle } from 'iconsax-react';
+import { Icon } from '@rkheftan/harmony-ui';
+import { type SocialMediaDialogProps } from '@/features/profile/types/settingsType';
+
+const MobileSlide = React.forwardRef(function MobileSlide(
+ props: TransitionProps & { children: ReactElement },
+ ref: React.Ref,
+) {
+ return ;
+});
+
+export default function SocialMediaDialog({
+ open,
+ onClose,
+ t,
+ emailInput,
+ setEmailInput,
+ fullScreen,
+ computedMaxWidth,
+ verificationCode,
+ setVerificationCode,
+ apiError,
+ isLoading,
+ dialogStep,
+ onSendCode,
+ onConfirmEmail,
+}: SocialMediaDialogProps) {
+ return (
+
+ );
+}
diff --git a/src/features/profile/components/userInformation/socialMedia/SocialMediaList.tsx b/src/features/profile/components/userInformation/socialMedia/SocialMediaList.tsx
new file mode 100644
index 0000000..85782c2
--- /dev/null
+++ b/src/features/profile/components/userInformation/socialMedia/SocialMediaList.tsx
@@ -0,0 +1,75 @@
+import { Box, Typography, IconButton } from '@mui/material';
+import { Google, Sms, Trash } from 'iconsax-react';
+import { Icon } from '@rkheftan/harmony-ui';
+import { type SocialMediaListProps } from '@/features/profile/types/settingsType';
+
+export default function SocialMediaList({ emailList }: SocialMediaListProps) {
+ return (
+
+ {emailList.map((item, index) => (
+
+
+
+ {item.provider === 'google' && (
+
+ )}
+ {item.provider === 'email' && (
+
+ )}
+
+
+
+
+ {item.email}
+
+
+ {item.time}
+
+
+
+
+
+
+
+
+ ))}
+
+ );
+}
diff --git a/src/features/profile/components/userInformation/socialMedia/SocialMediaMenu.tsx b/src/features/profile/components/userInformation/socialMedia/SocialMediaMenu.tsx
new file mode 100644
index 0000000..ebc3f9c
--- /dev/null
+++ b/src/features/profile/components/userInformation/socialMedia/SocialMediaMenu.tsx
@@ -0,0 +1,110 @@
+import React, { useState } from 'react';
+import {
+ Box,
+ Button,
+ Menu,
+ MenuItem,
+ ListItemIcon,
+ ListItemText,
+} from '@mui/material';
+import { Message, Google, Apple, ArrowDown3 } from 'iconsax-react';
+import { Icon } from '@rkheftan/harmony-ui';
+import { type SocialMediaMenuProps } from '@/features/profile/types/settingsType';
+
+export default function SocialMediaMenu({
+ t,
+ onOpenDialog,
+}: SocialMediaMenuProps) {
+ const [anchor, setAnchor] = useState(null);
+ const openMenu = Boolean(anchor);
+ const [open, setOpen] = useState(false);
+
+ const handleClickMenu = (e: React.MouseEvent) => {
+ setOpen(true);
+ setAnchor(e.currentTarget);
+ };
+ const handleCloseMenu = () => {
+ setOpen(false);
+ setAnchor(null);
+ };
+
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/features/profile/data/countries.ts b/src/features/profile/data/countries.ts
new file mode 100644
index 0000000..2b05f9e
--- /dev/null
+++ b/src/features/profile/data/countries.ts
@@ -0,0 +1,271 @@
+export interface Country {
+ code: string;
+ label: string;
+ phone: string;
+}
+
+export interface Country {
+ code: string;
+ label: string;
+ phone: string;
+}
+
+export const countries: readonly Country[] = [
+ { code: 'AF', label: 'country.afghanistan', phone: '+93' },
+ { code: 'AX', label: 'country.aland_islands', phone: '+358' },
+ { code: 'AL', label: 'country.albania', phone: '+355' },
+ { code: 'DZ', label: 'country.algeria', phone: '+213' },
+ { code: 'AS', label: 'country.american_samoa', phone: '+1684' },
+ { code: 'AD', label: 'country.andorra', phone: '+376' },
+ { code: 'AO', label: 'country.angola', phone: '+244' },
+ { code: 'AI', label: 'country.anguilla', phone: '+1264' },
+ { code: 'AQ', label: 'country.antarctica', phone: '+672' },
+ { code: 'AG', label: 'country.antigua_and_barbuda', phone: '+1268' },
+ { code: 'AR', label: 'country.argentina', phone: '+54' },
+ { code: 'AM', label: 'country.armenia', phone: '+374' },
+ { code: 'AW', label: 'country.aruba', phone: '+297' },
+ { code: 'AU', label: 'country.australia', phone: '+61' },
+ { code: 'AT', label: 'country.austria', phone: '+43' },
+ { code: 'AZ', label: 'country.azerbaijan', phone: '+994' },
+ { code: 'BS', label: 'country.bahamas', phone: '+1242' },
+ { code: 'BH', label: 'country.bahrain', phone: '+973' },
+ { code: 'BD', label: 'country.bangladesh', phone: '+880' },
+ { code: 'BB', label: 'country.barbados', phone: '+1246' },
+ { code: 'BY', label: 'country.belarus', phone: '+375' },
+ { code: 'BE', label: 'country.belgium', phone: '+32' },
+ { code: 'BZ', label: 'country.belize', phone: '+501' },
+ { code: 'BJ', label: 'country.benin', phone: '+229' },
+ { code: 'BM', label: 'country.bermuda', phone: '+1441' },
+ { code: 'BT', label: 'country.bhutan', phone: '+975' },
+ { code: 'BO', label: 'country.bolivia', phone: '+591' },
+ { code: 'BA', label: 'country.bosnia_and_herzegovina', phone: '+387' },
+ { code: 'BW', label: 'country.botswana', phone: '+267' },
+ { code: 'BR', label: 'country.brazil', phone: '+55' },
+ {
+ code: 'IO',
+ label: 'country.british_indian_ocean_territory',
+ phone: '+246',
+ },
+ { code: 'VG', label: 'country.british_virgin_islands', phone: '+1284' },
+ { code: 'BN', label: 'country.brunei', phone: '+673' },
+ { code: 'BG', label: 'country.bulgaria', phone: '+359' },
+ { code: 'BF', label: 'country.burkina_faso', phone: '+226' },
+ { code: 'BI', label: 'country.burundi', phone: '+257' },
+ { code: 'KH', label: 'country.cambodia', phone: '+855' },
+ { code: 'CM', label: 'country.cameroon', phone: '+237' },
+ { code: 'CA', label: 'country.canada', phone: '+1' },
+ { code: 'CV', label: 'country.cape_verde', phone: '+238' },
+ { code: 'KY', label: 'country.cayman_islands', phone: '+1345' },
+ { code: 'CF', label: 'country.central_african_republic', phone: '+236' },
+ { code: 'TD', label: 'country.chad', phone: '+235' },
+ { code: 'CL', label: 'country.chile', phone: '+56' },
+ { code: 'CN', label: 'country.china', phone: '+86' },
+ { code: 'CX', label: 'country.christmas_island', phone: '+61' },
+ { code: 'CC', label: 'country.cocos_keeling_islands', phone: '+61' },
+ { code: 'CO', label: 'country.colombia', phone: '+57' },
+ { code: 'KM', label: 'country.comoros', phone: '+269' },
+ { code: 'CG', label: 'country.congo_brazzaville', phone: '+242' },
+ { code: 'CD', label: 'country.congo_kinshasa', phone: '+243' },
+ { code: 'CK', label: 'country.cook_islands', phone: '+682' },
+ { code: 'CR', label: 'country.costa_rica', phone: '+506' },
+ { code: 'CI', label: 'country.cote_divoire', phone: '+225' },
+ { code: 'HR', label: 'country.croatia', phone: '+385' },
+ { code: 'CU', label: 'country.cuba', phone: '+53' },
+ { code: 'CW', label: 'country.curacao', phone: '+599' },
+ { code: 'CY', label: 'country.cyprus', phone: '+357' },
+ { code: 'CZ', label: 'country.czech_republic', phone: '+420' },
+ { code: 'DK', label: 'country.denmark', phone: '+45' },
+ { code: 'DJ', label: 'country.djibouti', phone: '+253' },
+ { code: 'DM', label: 'country.dominica', phone: '+1767' },
+ { code: 'DO', label: 'country.dominican_republic', phone: '+1' },
+ { code: 'EC', label: 'country.ecuador', phone: '+593' },
+ { code: 'EG', label: 'country.egypt', phone: '+20' },
+ { code: 'SV', label: 'country.el_salvador', phone: '+503' },
+ { code: 'GQ', label: 'country.equatorial_guinea', phone: '+240' },
+ { code: 'ER', label: 'country.eritrea', phone: '+291' },
+ { code: 'EE', label: 'country.estonia', phone: '+372' },
+ { code: 'SZ', label: 'country.eswatini', phone: '+268' },
+ { code: 'ET', label: 'country.ethiopia', phone: '+251' },
+ { code: 'FK', label: 'country.falkland_islands', phone: '+500' },
+ { code: 'FO', label: 'country.faroe_islands', phone: '+298' },
+ { code: 'FJ', label: 'country.fiji', phone: '+679' },
+ { code: 'FI', label: 'country.finland', phone: '+358' },
+ { code: 'FR', label: 'country.france', phone: '+33' },
+ { code: 'GF', label: 'country.french_guiana', phone: '+594' },
+ { code: 'PF', label: 'country.french_polynesia', phone: '+689' },
+ { code: 'GA', label: 'country.gabon', phone: '+241' },
+ { code: 'GM', label: 'country.gambia', phone: '+220' },
+ { code: 'GE', label: 'country.georgia', phone: '+995' },
+ { code: 'DE', label: 'country.germany', phone: '+49' },
+ { code: 'GH', label: 'country.ghana', phone: '+233' },
+ { code: 'GI', label: 'country.gibraltar', phone: '+350' },
+ { code: 'GR', label: 'country.greece', phone: '+30' },
+ { code: 'GL', label: 'country.greenland', phone: '+299' },
+ { code: 'GD', label: 'country.grenada', phone: '+1473' },
+ { code: 'GP', label: 'country.guadeloupe', phone: '+590' },
+ { code: 'GU', label: 'country.guam', phone: '+1671' },
+ { code: 'GT', label: 'country.guatemala', phone: '+502' },
+ { code: 'GG', label: 'country.guernsey', phone: '+44' },
+ { code: 'GN', label: 'country.guinea', phone: '+224' },
+ { code: 'GW', label: 'country.guinea_bissau', phone: '+245' },
+ { code: 'GY', label: 'country.guyana', phone: '+592' },
+ { code: 'HT', label: 'country.haiti', phone: '+509' },
+ { code: 'HN', label: 'country.honduras', phone: '+504' },
+ { code: 'HK', label: 'country.hong_kong', phone: '+852' },
+ { code: 'HU', label: 'country.hungary', phone: '+36' },
+ { code: 'IS', label: 'country.iceland', phone: '+354' },
+ { code: 'IN', label: 'country.india', phone: '+91' },
+ { code: 'ID', label: 'country.indonesia', phone: '+62' },
+ { code: 'IR', label: 'country.iran', phone: '+98' },
+ { code: 'IQ', label: 'country.iraq', phone: '+964' },
+ { code: 'IE', label: 'country.ireland', phone: '+353' },
+ { code: 'IM', label: 'country.isle_of_man', phone: '+44' },
+ { code: 'IL', label: 'country.israel', phone: '+972' },
+ { code: 'IT', label: 'country.italy', phone: '+39' },
+ { code: 'JM', label: 'country.jamaica', phone: '+1876' },
+ { code: 'JP', label: 'country.japan', phone: '+81' },
+ { code: 'JE', label: 'country.jersey', phone: '+44' },
+ { code: 'JO', label: 'country.jordan', phone: '+962' },
+ { code: 'KZ', label: 'country.kazakhstan', phone: '+7' },
+ { code: 'KE', label: 'country.kenya', phone: '+254' },
+ { code: 'KI', label: 'country.kiribati', phone: '+686' },
+ { code: 'XK', label: 'country.kosovo', phone: '+383' },
+ { code: 'KW', label: 'country.kuwait', phone: '+965' },
+ { code: 'KG', label: 'country.kyrgyzstan', phone: '+996' },
+ { code: 'LA', label: 'country.laos', phone: '+856' },
+ { code: 'LV', label: 'country.latvia', phone: '+371' },
+ { code: 'LB', label: 'country.lebanon', phone: '+961' },
+ { code: 'LS', label: 'country.lesotho', phone: '+266' },
+ { code: 'LR', label: 'country.liberia', phone: '+231' },
+ { code: 'LY', label: 'country.libya', phone: '+218' },
+ { code: 'LI', label: 'country.liechtenstein', phone: '+423' },
+ { code: 'LT', label: 'country.lithuania', phone: '+370' },
+ { code: 'LU', label: 'country.luxembourg', phone: '+352' },
+ { code: 'MO', label: 'country.macau', phone: '+853' },
+ { code: 'MG', label: 'country.madagascar', phone: '+261' },
+ { code: 'MW', label: 'country.malawi', phone: '+265' },
+ { code: 'MY', label: 'country.malaysia', phone: '+60' },
+ { code: 'MV', label: 'country.maldives', phone: '+960' },
+ { code: 'ML', label: 'country.mali', phone: '+223' },
+ { code: 'MT', label: 'country.malta', phone: '+356' },
+ { code: 'MH', label: 'country.marshall_islands', phone: '+692' },
+ { code: 'MQ', label: 'country.martinique', phone: '+596' },
+ { code: 'MR', label: 'country.mauritania', phone: '+222' },
+ { code: 'MU', label: 'country.mauritius', phone: '+230' },
+ { code: 'YT', label: 'country.mayotte', phone: '+262' },
+ { code: 'MX', label: 'country.mexico', phone: '+52' },
+ { code: 'FM', label: 'country.micronesia', phone: '+691' },
+ { code: 'MD', label: 'country.moldova', phone: '+373' },
+ { code: 'MC', label: 'country.monaco', phone: '+377' },
+ { code: 'MN', label: 'country.mongolia', phone: '+976' },
+ { code: 'ME', label: 'country.montenegro', phone: '+382' },
+ { code: 'MS', label: 'country.montserrat', phone: '+1664' },
+ { code: 'MA', label: 'country.morocco', phone: '+212' },
+ { code: 'MZ', label: 'country.mozambique', phone: '+258' },
+ { code: 'MM', label: 'country.myanmar', phone: '+95' },
+ { code: 'NA', label: 'country.namibia', phone: '+264' },
+ { code: 'NR', label: 'country.nauru', phone: '+674' },
+ { code: 'NP', label: 'country.nepal', phone: '+977' },
+ { code: 'NL', label: 'country.netherlands', phone: '+31' },
+ { code: 'NC', label: 'country.new_caledonia', phone: '+687' },
+ { code: 'NZ', label: 'country.new_zealand', phone: '+64' },
+ { code: 'NI', label: 'country.nicaragua', phone: '+505' },
+ { code: 'NE', label: 'country.niger', phone: '+227' },
+ { code: 'NG', label: 'country.nigeria', phone: '+234' },
+ { code: 'NU', label: 'country.niue', phone: '+683' },
+ { code: 'NF', label: 'country.norfolk_island', phone: '+672' },
+ { code: 'KP', label: 'country.north_korea', phone: '+850' },
+ { code: 'MK', label: 'country.north_macedonia', phone: '+389' },
+ { code: 'MP', label: 'country.northern_mariana_islands', phone: '+1670' },
+ { code: 'NO', label: 'country.norway', phone: '+47' },
+ { code: 'OM', label: 'country.oman', phone: '+968' },
+ { code: 'PK', label: 'country.pakistan', phone: '+92' },
+ { code: 'PW', label: 'country.palau', phone: '+680' },
+ { code: 'PS', label: 'country.palestine', phone: '+970' },
+ { code: 'PA', label: 'country.panama', phone: '+507' },
+ { code: 'PG', label: 'country.papua_new_guinea', phone: '+675' },
+ { code: 'PY', label: 'country.paraguay', phone: '+595' },
+ { code: 'PE', label: 'country.peru', phone: '+51' },
+ { code: 'PH', label: 'country.philippines', phone: '+63' },
+ { code: 'PN', label: 'country.pitcairn_islands', phone: '+64' },
+ { code: 'PL', label: 'country.poland', phone: '+48' },
+ { code: 'PT', label: 'country.portugal', phone: '+351' },
+ { code: 'PR', label: 'country.puerto_rico', phone: '+1' },
+ { code: 'QA', label: 'country.qatar', phone: '+974' },
+ { code: 'RE', label: 'country.reunion', phone: '+262' },
+ { code: 'RO', label: 'country.romania', phone: '+40' },
+ { code: 'RU', label: 'country.russia', phone: '+7' },
+ { code: 'RW', label: 'country.rwanda', phone: '+250' },
+ { code: 'BL', label: 'country.saint_barthelemy', phone: '+590' },
+ { code: 'SH', label: 'country.saint_helena', phone: '+290' },
+ { code: 'KN', label: 'country.saint_kitts_and_nevis', phone: '+1869' },
+ { code: 'LC', label: 'country.saint_lucia', phone: '+1758' },
+ { code: 'MF', label: 'country.saint_martin', phone: '+590' },
+ { code: 'PM', label: 'country.saint_pierre_and_miquelon', phone: '+508' },
+ {
+ code: 'VC',
+ label: 'country.saint_vincent_and_the_grenadines',
+ phone: '+1784',
+ },
+ { code: 'WS', label: 'country.samoa', phone: '+685' },
+ { code: 'SM', label: 'country.san_marino', phone: '+378' },
+ { code: 'ST', label: 'country.sao_tome_and_principe', phone: '+239' },
+ { code: 'SA', label: 'country.saudi_arabia', phone: '+966' },
+ { code: 'SN', label: 'country.senegal', phone: '+221' },
+ { code: 'RS', label: 'country.serbia', phone: '+381' },
+ { code: 'SC', label: 'country.seychelles', phone: '+248' },
+ { code: 'SL', label: 'country.sierra_leone', phone: '+232' },
+ { code: 'SG', label: 'country.singapore', phone: '+65' },
+ { code: 'SX', label: 'country.sint_maarten', phone: '+1721' },
+ { code: 'SK', label: 'country.slovakia', phone: '+421' },
+ { code: 'SI', label: 'country.slovenia', phone: '+386' },
+ { code: 'SB', label: 'country.solomon_islands', phone: '+677' },
+ { code: 'SO', label: 'country.somalia', phone: '+252' },
+ { code: 'ZA', label: 'country.south_africa', phone: '+27' },
+ {
+ code: 'GS',
+ label: 'country.south_georgia_and_south_sandwich_islands',
+ phone: '+500',
+ },
+ { code: 'KR', label: 'country.south_korea', phone: '+82' },
+ { code: 'SS', label: 'country.south_sudan', phone: '+211' },
+ { code: 'ES', label: 'country.spain', phone: '+34' },
+ { code: 'LK', label: 'country.sri_lanka', phone: '+94' },
+ { code: 'SD', label: 'country.sudan', phone: '+249' },
+ { code: 'SR', label: 'country.suriname', phone: '+597' },
+ { code: 'SJ', label: 'country.svalbard_and_jan_mayen', phone: '+47' },
+ { code: 'SE', label: 'country.sweden', phone: '+46' },
+ { code: 'CH', label: 'country.switzerland', phone: '+41' },
+ { code: 'SY', label: 'country.syria', phone: '+963' },
+ { code: 'TW', label: 'country.taiwan', phone: '+886' },
+ { code: 'TJ', label: 'country.tajikistan', phone: '+992' },
+ { code: 'TZ', label: 'country.tanzania', phone: '+255' },
+ { code: 'TH', label: 'country.thailand', phone: '+66' },
+ { code: 'TL', label: 'country.timor_leste', phone: '+670' },
+ { code: 'TG', label: 'country.togo', phone: '+228' },
+ { code: 'TK', label: 'country.tokelau', phone: '+690' },
+ { code: 'TO', label: 'country.tonga', phone: '+676' },
+ { code: 'TT', label: 'country.trinidad_and_tobago', phone: '+1868' },
+ { code: 'TN', label: 'country.tunisia', phone: '+216' },
+ { code: 'TR', label: 'country.turkey', phone: '+90' },
+ { code: 'TM', label: 'country.turkmenistan', phone: '+993' },
+ { code: 'TC', label: 'country.turks_and_caicos_islands', phone: '+1649' },
+ { code: 'TV', label: 'country.tuvalu', phone: '+688' },
+ { code: 'VI', label: 'country.us_virgin_islands', phone: '+1340' },
+ { code: 'UG', label: 'country.uganda', phone: '+256' },
+ { code: 'UA', label: 'country.ukraine', phone: '+380' },
+ { code: 'AE', label: 'country.united_arab_emirates', phone: '+971' },
+ { code: 'GB', label: 'country.united_kingdom', phone: '+44' },
+ { code: 'US', label: 'country.united_states', phone: '+1' },
+ { code: 'UY', label: 'country.uruguay', phone: '+598' },
+ { code: 'UZ', label: 'country.uzbekistan', phone: '+998' },
+ { code: 'VU', label: 'country.vanuatu', phone: '+678' },
+ { code: 'VA', label: 'country.vatican_city', phone: '+39' },
+ { code: 'VE', label: 'country.venezuela', phone: '+58' },
+ { code: 'VN', label: 'country.vietnam', phone: '+84' },
+ { code: 'WF', label: 'country.wallis_and_futuna', phone: '+681' },
+ { code: 'EH', label: 'country.western_sahara', phone: '+212' },
+ { code: 'YE', label: 'country.yemen', phone: '+967' },
+ { code: 'ZM', label: 'country.zambia', phone: '+260' },
+ { code: 'ZW', label: 'country.zimbabwe', phone: '+263' },
+];
diff --git a/src/features/profile/types/settingsApiType.ts b/src/features/profile/types/settingsApiType.ts
new file mode 100644
index 0000000..4edc7de
--- /dev/null
+++ b/src/features/profile/types/settingsApiType.ts
@@ -0,0 +1,91 @@
+import { Gender } from './settingsType';
+
+/* General API Types */
+export interface UserSettingsFromApi {
+ theme: number;
+ calendarType: number;
+ language: number;
+}
+export interface ApiSession {
+ key: string;
+ created: string;
+ deviceOs: string;
+ deviceName: string;
+ ipAddress: string;
+}
+export interface ActiveSessionsData {
+ sessions: ApiSession[];
+ currentKey: string;
+}
+export interface LoginLog {
+ loginDateTime: string;
+ ipAddress: string;
+ deviceName: string;
+ deviceOs: string;
+}
+
+/* Profile API Types */
+export interface GetProfileApiResponse {
+ success: boolean;
+ message?: string;
+ firstName?: string;
+ lastName?: string;
+ nationalCode?: string;
+ gender?: Gender;
+ countryCode?: string;
+ profileImageUrl?: string;
+ phoneNumber?: string;
+ userSettings?: UserSettingsFromApi;
+ activeSessions?: ActiveSessionsData;
+ loginLogs?: LoginLog[];
+ email?: string;
+ hasPassword?: boolean;
+}
+export interface SaveProfileApiResponse {
+ success: boolean;
+ message?: string;
+ phoneNumber?: string;
+ email?: string;
+}
+
+/* Settings API Types */
+export interface SaveSettingsApiResponse {
+ success: boolean;
+ message?: string;
+}
+
+/* Session API Types */
+export interface DeleteSessionsApiResponse {
+ success: boolean;
+ message?: string;
+}
+
+/* Password API Types */
+export interface PasswordApiResponse {
+ success: boolean;
+ message?: string;
+}
+
+/* Email API Types */
+export interface SendEmailCodeApiResponse {
+ success: boolean;
+ message?: string;
+}
+export interface ConfirmEmailCodeApiResponse {
+ success: boolean;
+ confirm?: boolean;
+ message?: string;
+}
+export interface ChangeEmailApiResponse {
+ success: boolean;
+ message?: string;
+}
+
+/* Phone Number API Types */
+export interface PhoneNumberApiResponse {
+ success: boolean;
+ message?: string;
+}
+export interface ConfirmPhoneNumberApiResponse extends PhoneNumberApiResponse {
+ confirm?: boolean;
+}
diff --git a/src/features/profile/types/settingsType.ts b/src/features/profile/types/settingsType.ts
new file mode 100644
index 0000000..fac5e45
--- /dev/null
+++ b/src/features/profile/types/settingsType.ts
@@ -0,0 +1,146 @@
+export enum Gender {
+ Male = 2,
+ Female = 1,
+ None = 0,
+}
+
+export interface InfoRowData {
+ firstName: string;
+ lastName: string;
+ nationalCode: string;
+ country: string | '';
+ gender: Gender;
+}
+
+export interface Device {
+ id: string;
+ timeAndDate: string;
+ deviceModel: string;
+ ip: string;
+ current: boolean;
+}
+
+export interface PasswordDialogProps {
+ open: boolean;
+ fullScreen: boolean;
+ handleClose: () => void;
+ password: string;
+ setPassword: (val: string) => void;
+ confirmPassword: string;
+ setConfirmPassword: (val: string) => void;
+ currentPassword: string;
+ setCurrentPassword: (val: string) => void;
+ showValidation: boolean;
+ validPassword: boolean;
+ matchPassword: boolean;
+ loading: boolean;
+ handleSubmit: () => void;
+ changePassword: boolean;
+ apiError: string | null;
+ t: (key: string) => string;
+}
+
+export interface ValidationItemProps {
+ isValid: boolean;
+ label: string;
+}
+
+export interface SocialMediaDialogProps {
+ open: boolean;
+ onClose: () => void;
+ t: (key: string) => string;
+ emailInput: string;
+ setEmailInput: (val: string) => void;
+ fullScreen: boolean;
+ computedMaxWidth: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
+ verificationCode: string;
+ setVerificationCode: (value: string) => void;
+ apiError: string | null;
+ isLoading: boolean;
+ dialogStep: 'enterEmail' | 'enterCode';
+ onSendCode: () => void;
+ onConfirmEmail: () => void;
+}
+
+export interface SocialMediaListProps {
+ t: (key: string) => string;
+ emailList: readonly {
+ email: string;
+ provider: 'email' | 'google' | 'apple';
+ time: string;
+ }[];
+}
+
+export interface SocialMediaMenuProps {
+ t: (key: string) => string;
+ onOpenDialog: () => void;
+}
+
+export interface Phone {
+ phone: string;
+ time: string;
+ withCode: string;
+}
+
+export interface EmailAccount {
+ email: string;
+ provider: 'email' | 'google';
+ time: string;
+}
+
+export interface PhoneActionButtonsProps {
+ isEditing: boolean;
+ toggleEdit: () => void;
+ t: (key: string) => string;
+}
+
+export interface PhoneDisplayProps {
+ phones: { phone: string; time: string }[];
+}
+
+export interface PhoneEditFormProps {
+ phoneNumber: string;
+ setPhoneNumber: (v: string) => void;
+ countryCode: string;
+ setCountryCode: (v: string) => void;
+ verificationCode: string;
+ setVerificationCode: (v: string) => void;
+ isVerified: boolean;
+ isVerifying: boolean;
+ buttonState: 'default' | 'counting';
+ setButtonState: (v: 'default' | 'counting') => void;
+ handleSendCode: () => void;
+ handleVerifyClick: () => void;
+ error?: string;
+ inputError: boolean;
+ handleBlur: () => void;
+ textFieldRef: React.RefObject;
+ inputRef: React.RefObject;
+ phones: { phone: string; time: string; withCode: string }[];
+ showToast: boolean;
+ setShowToast: (v: boolean) => void;
+ t: (key: string) => string;
+}
+
+export interface DisplayFieldProps {
+ label: string;
+ value: string;
+}
+
+export interface InfoRowDisplayProps {
+ data: InfoRowData;
+ uploadedImageUrl: string | null;
+ initials: string;
+}
+
+export interface InfoRowEditProps {
+ data: InfoRowData;
+ setData: React.Dispatch>;
+}
+
+export interface ProfileImageProps {
+ initials: string;
+ uploadedImageUrl: string | null;
+ onImageChange: (file: File) => void;
+ onRemoveImage?: () => void;
+}
diff --git a/src/hooks/useApi.ts b/src/hooks/useApi.ts
index ff7c394..ff98bd0 100644
--- a/src/hooks/useApi.ts
+++ b/src/hooks/useApi.ts
@@ -1,17 +1,17 @@
-import type { ApiResponse } from '@/types/apiResponse';
+// FIXME: all the responses should extend this interface
+// import type { ApiResponse } from '@/types/apiResponse';
import { useToast } from '@rkheftan/harmony-ui';
import type { AxiosError } from 'axios';
import { useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
-
// Define options for the hook
interface UseApiOptions {
// If true, the API call will be executed immediately on mount
immediate?: boolean;
}
-export function useApi(
+export function useApi(
apiFunction: (...args: P) => Promise<{ data: T }>,
options: UseApiOptions = {},
) {
@@ -51,7 +51,8 @@ export function useApi(
}
},
[apiFunction, navigate, t, toast],
- );
+ )
+
// If the 'immediate' option is true, execute the function on mount
useEffect(() => {
diff --git a/src/providers/CustomThemeProvider.tsx b/src/providers/CustomThemeProvider.tsx
index 374cea1..b98ac00 100644
--- a/src/providers/CustomThemeProvider.tsx
+++ b/src/providers/CustomThemeProvider.tsx
@@ -25,23 +25,36 @@ export const CustomThemeProvider: React.FC<{ children: React.ReactNode }> = ({
cssVariables: {
colorSchemeSelector: 'class',
},
+ spacing: 8,
+ typography: typography,
components: {
+ MuiButton: {
+ defaultProps: {
+ size: 'large',
+ fullWidth: true,
+ variant: 'contained',
+ sx: {
+ textTransform: 'none',
+ },
+ },
+ },
MuiTextField: {
defaultProps: {
variant: 'outlined',
fullWidth: true,
},
},
- MuiButton: {
- defaultProps: {
- size: 'large',
- fullWidth: true,
- variant: 'contained',
+ MuiSelect: {
+ styleOverrides: {
+ root: {
+ textAlign: 'left',
+ },
+ select: {
+ textAlign: 'left',
+ },
},
},
},
- spacing: 8,
- typography: typography,
});
}, [i18n]);
diff --git a/src/utils/formatSessionDate.tsx b/src/utils/formatSessionDate.tsx
new file mode 100644
index 0000000..eb49453
--- /dev/null
+++ b/src/utils/formatSessionDate.tsx
@@ -0,0 +1,38 @@
+import { type TFunction } from 'i18next';
+import { toLocaleDigits } from './persianDigit';
+
+export function formatDate(
+ isoDate: string,
+ lang: string,
+ t: TFunction,
+): string {
+ const date = new Date(isoDate);
+ const now = new Date();
+ const diffInMinutes = Math.floor(
+ (now.getTime() - date.getTime()) / (1000 * 60),
+ );
+
+ if (diffInMinutes < 1) return t('active.justNow');
+ if (diffInMinutes < 60) {
+ return toLocaleDigits(
+ t('active.minutesAgo', { count: diffInMinutes }),
+ lang,
+ );
+ }
+
+ const options: Intl.DateTimeFormatOptions = {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ hour12: false,
+ calendar: lang.startsWith('fa') ? 'persian' : 'gregory',
+ numberingSystem: lang.startsWith('fa') ? 'arab' : 'latn',
+ };
+ const displayLocale = lang.startsWith('fa') ? 'fa-IR' : 'en-US';
+ const formattedDate = new Intl.DateTimeFormat(displayLocale, options).format(
+ date,
+ );
+ return toLocaleDigits(formattedDate, lang);
+}
diff --git a/src/utils/persianDigit.tsx b/src/utils/persianDigit.tsx
new file mode 100644
index 0000000..280253f
--- /dev/null
+++ b/src/utils/persianDigit.tsx
@@ -0,0 +1,12 @@
+export const toLocaleDigits = (
+ input: string | number,
+ lang: string,
+): string => {
+ const str = String(input);
+
+ if (lang.startsWith('fa')) {
+ return str.replace(/\d/g, (d: string) => '۰۱۲۳۴۵۶۷۸۹'[parseInt(d, 10)]);
+ }
+
+ return str;
+};
diff --git a/src/utils/regex.ts b/src/utils/regex.ts
new file mode 100644
index 0000000..a4c68f4
--- /dev/null
+++ b/src/utils/regex.ts
@@ -0,0 +1,15 @@
+export function regex(password: string) {
+ const hasNumber = /\d/.test(password);
+ const hasMinLength = password.length >= 8;
+ const hasUpperAndLower = /[A-Z]/.test(password) && /[a-z]/.test(password);
+ const hasSpecialChar = /[!@#$%^&*]/.test(password);
+
+ return {
+ hasNumber,
+ hasMinLength,
+ hasUpperAndLower,
+ hasSpecialChar,
+ validPassword:
+ hasNumber && hasMinLength && hasUpperAndLower && hasSpecialChar,
+ };
+}
diff --git a/tsconfig.app.json b/tsconfig.app.json
index 13ecaed..7c8c4f8 100644
--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -6,6 +6,7 @@
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"module": "ESNext",
+ "skipLibCheck": true,
/* Bundler & Module Resolution */
"moduleResolution": "bundler",