diff --git a/package-lock.json b/package-lock.json
index 895fb99..20a2b64 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -156,6 +156,15 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/helper-module-imports": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
@@ -327,19 +336,10 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/traverse/node_modules/globals": {
- "version": "11.12.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
- "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/@babel/types": {
- "version": "7.27.7",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz",
- "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==",
+ "version": "7.28.2",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
+ "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
@@ -1297,9 +1297,9 @@
}
},
"node_modules/@mui/core-downloads-tracker": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.2.0.tgz",
- "integrity": "sha512-d49s7kEgI5iX40xb2YPazANvo7Bx0BLg/MNRwv+7BVpZUzXj1DaVCKlQTDex3gy/0jsCb4w7AY2uH4t4AJvSog==",
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.1.tgz",
+ "integrity": "sha512-+mIK1Z0BhOaQ0vCgOkT1mSrIpEHLo338h4/duuL4TBLXPvUMit732mnwJY3W40Avy30HdeSfwUAAGRkKmwRaEQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -1307,22 +1307,22 @@
}
},
"node_modules/@mui/material": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.2.0.tgz",
- "integrity": "sha512-NTuyFNen5Z2QY+I242MDZzXnFIVIR6ERxo7vntFi9K1wCgSwvIl0HcAO2OOydKqqKApE6omRiYhpny1ZhGuH7Q==",
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.1.tgz",
+ "integrity": "sha512-Xf6Shbo03YmcBedZMwSpEFOwpYDtU7tC+rhAHTrA9FHk0FpsDqiQ9jUa1j/9s3HLs7KWb5mDcGnlwdh9Q9KAag==",
"license": "MIT",
"dependencies": {
- "@babel/runtime": "^7.27.6",
- "@mui/core-downloads-tracker": "^7.2.0",
- "@mui/system": "^7.2.0",
- "@mui/types": "^7.4.4",
- "@mui/utils": "^7.2.0",
+ "@babel/runtime": "^7.28.2",
+ "@mui/core-downloads-tracker": "^7.3.1",
+ "@mui/system": "^7.3.1",
+ "@mui/types": "^7.4.5",
+ "@mui/utils": "^7.3.1",
"@popperjs/core": "^2.11.8",
"@types/react-transition-group": "^4.4.12",
"clsx": "^2.1.1",
"csstype": "^3.1.3",
"prop-types": "^15.8.1",
- "react-is": "^19.1.0",
+ "react-is": "^19.1.1",
"react-transition-group": "^4.4.5"
},
"engines": {
@@ -1335,7 +1335,7 @@
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
- "@mui/material-pigment-css": "^7.2.0",
+ "@mui/material-pigment-css": "^7.3.1",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -1356,13 +1356,13 @@
}
},
"node_modules/@mui/private-theming": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.2.0.tgz",
- "integrity": "sha512-y6N1Yt3T5RMxVFnCh6+zeSWBuQdNDm5/UlM0EAYZzZR/1u+XKJWYQmbpx4e+F+1EpkYi3Nk8KhPiQDi83M3zIw==",
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.1.tgz",
+ "integrity": "sha512-WU3YLkKXii/x8ZEKnrLKsPwplCVE11yZxUvlaaZSIzCcI3x2OdFC8eMlNy74hVeUsYQvzzX1Es/k4ARPlFvpPQ==",
"license": "MIT",
"dependencies": {
- "@babel/runtime": "^7.27.6",
- "@mui/utils": "^7.2.0",
+ "@babel/runtime": "^7.28.2",
+ "@mui/utils": "^7.3.1",
"prop-types": "^15.8.1"
},
"engines": {
@@ -1383,12 +1383,12 @@
}
},
"node_modules/@mui/styled-engine": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.2.0.tgz",
- "integrity": "sha512-yq08xynbrNYcB1nBcW9Fn8/h/iniM3ewRguGJXPIAbHvxEF7Pz95kbEEOAAhwzxMX4okhzvHmk0DFuC5ayvgIQ==",
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.1.tgz",
+ "integrity": "sha512-Nqo6OHjvJpXJ1+9TekTE//+8RybgPQUKwns2Lh0sq+8rJOUSUKS3KALv4InSOdHhIM9Mdi8/L7LTF1/Ky6D6TQ==",
"license": "MIT",
"dependencies": {
- "@babel/runtime": "^7.27.6",
+ "@babel/runtime": "^7.28.2",
"@emotion/cache": "^11.14.0",
"@emotion/serialize": "^1.3.3",
"@emotion/sheet": "^1.4.0",
@@ -1437,16 +1437,16 @@
}
},
"node_modules/@mui/system": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.2.0.tgz",
- "integrity": "sha512-PG7cm/WluU6RAs+gNND2R9vDwNh+ERWxPkqTaiXQJGIFAyJ+VxhyKfzpdZNk0z0XdmBxxi9KhFOpgxjehf/O0A==",
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.1.tgz",
+ "integrity": "sha512-mIidecvcNVpNJMdPDmCeoSL5zshKBbYPcphjuh6ZMjhybhqhZ4mX6k9zmIWh6XOXcqRQMg5KrcjnO0QstrNj3w==",
"license": "MIT",
"dependencies": {
- "@babel/runtime": "^7.27.6",
- "@mui/private-theming": "^7.2.0",
- "@mui/styled-engine": "^7.2.0",
- "@mui/types": "^7.4.4",
- "@mui/utils": "^7.2.0",
+ "@babel/runtime": "^7.28.2",
+ "@mui/private-theming": "^7.3.1",
+ "@mui/styled-engine": "^7.3.1",
+ "@mui/types": "^7.4.5",
+ "@mui/utils": "^7.3.1",
"clsx": "^2.1.1",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
@@ -1477,12 +1477,12 @@
}
},
"node_modules/@mui/types": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.4.tgz",
- "integrity": "sha512-p63yhbX52MO/ajXC7hDHJA5yjzJekvWD3q4YDLl1rSg+OXLczMYPvTuSuviPRCgRX8+E42RXz1D/dz9SxPSlWg==",
+ "version": "7.4.5",
+ "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.5.tgz",
+ "integrity": "sha512-ZPwlAOE3e8C0piCKbaabwrqZbW4QvWz0uapVPWya7fYj6PeDkl5sSJmomT7wjOcZGPB48G/a6Ubidqreptxz4g==",
"license": "MIT",
"dependencies": {
- "@babel/runtime": "^7.27.6"
+ "@babel/runtime": "^7.28.2"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -1494,17 +1494,17 @@
}
},
"node_modules/@mui/utils": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.2.0.tgz",
- "integrity": "sha512-O0i1GQL6MDzhKdy9iAu5Yr0Sz1wZjROH1o3aoztuivdCXqEeQYnEjTDiRLGuFxI9zrUbTHBwobMyQH5sNtyacw==",
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.1.tgz",
+ "integrity": "sha512-/31y4wZqVWa0jzMnzo6JPjxwP6xXy4P3+iLbosFg/mJQowL1KIou0LC+lquWW60FKVbKz5ZUWBg2H3jausa0pw==",
"license": "MIT",
"dependencies": {
- "@babel/runtime": "^7.27.6",
- "@mui/types": "^7.4.4",
+ "@babel/runtime": "^7.28.2",
+ "@mui/types": "^7.4.5",
"@types/prop-types": "^15.7.15",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
- "react-is": "^19.1.0"
+ "react-is": "^19.1.1"
},
"engines": {
"node": ">=14.0.0"
@@ -1973,9 +1973,9 @@
}
},
"node_modules/@types/react-dom": {
- "version": "19.1.6",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz",
- "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==",
+ "version": "19.1.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz",
+ "integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -2352,7 +2352,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -2368,8 +2367,14 @@
"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",
- "peer": true
+ "license": "MIT"
+ },
+ "node_modules/ajv/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,
+ "license": "MIT"
},
"node_modules/ansi-styles": {
"version": "4.3.0",
@@ -2475,12 +2480,6 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"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/atomically": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/atomically/-/atomically-2.0.3.tgz",
@@ -2585,6 +2584,7 @@
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
@@ -2863,7 +2863,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -2879,15 +2878,13 @@
"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",
- "peer": true
+ "license": "MIT"
},
"node_modules/conf/node_modules/semver": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"license": "ISC",
- "peer": true,
"bin": {
"semver": "bin/semver.js"
},
@@ -4279,12 +4276,6 @@
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"license": "MIT"
},
- "node_modules/json-schema-traverse": {
- "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",
@@ -4640,6 +4631,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
@@ -5098,9 +5090,9 @@
}
},
"node_modules/react-is": {
- "version": "19.1.0",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
- "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==",
+ "version": "19.1.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz",
+ "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==",
"license": "MIT"
},
"node_modules/react-refresh": {
@@ -5195,7 +5187,6 @@
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
diff --git a/src/components/CountryFlag.tsx b/src/components/CountryFlag.tsx
deleted file mode 100644
index ed1bbee..0000000
--- a/src/components/CountryFlag.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-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/components/DigitsInput.tsx b/src/components/DigitsInput.tsx
similarity index 100%
rename from src/components/components/DigitsInput.tsx
rename to src/components/DigitsInput.tsx
diff --git a/src/components/components/common/Container.tsx b/src/components/common/Container.tsx
similarity index 100%
rename from src/components/components/common/Container.tsx
rename to src/components/common/Container.tsx
diff --git a/src/components/components/common/FlexBox.tsx b/src/components/common/FlexBox.tsx
similarity index 100%
rename from src/components/components/common/FlexBox.tsx
rename to src/components/common/FlexBox.tsx
diff --git a/src/components/components/common/Stack.tsx b/src/components/common/Stack.tsx
similarity index 100%
rename from src/components/components/common/Stack.tsx
rename to src/components/common/Stack.tsx
diff --git a/src/components/index.ts b/src/components/index.ts
new file mode 100644
index 0000000..f71515c
--- /dev/null
+++ b/src/components/index.ts
@@ -0,0 +1 @@
+export * from './layout/Layout';
diff --git a/src/countries.ts b/src/data/countries.ts
similarity index 100%
rename from src/countries.ts
rename to src/data/countries.ts
diff --git a/src/features/authentication/api/userCompletion.ts b/src/features/authentication/api/userCompletion.ts
index d9fcf26..83f96ee 100644
--- a/src/features/authentication/api/userCompletion.ts
+++ b/src/features/authentication/api/userCompletion.ts
@@ -1,12 +1,37 @@
import {
type SendEmailOtpPayload,
+ type TokenApiResponse,
type ConfirmEmailOtpPayload,
type CompleteUserInfoPayload,
type CompleteUserInfoResponse,
type GenericApiResponse,
} from '../types/completionFormApiTypes';
+import axios from 'axios';
import apiClient from '@/lib/apiClient';
+const AUTH_API_URL = 'https://accounts.business-harmony.com';
+
+export const getTokenApi = async (): Promise => {
+ const body = new URLSearchParams();
+ body.set('grant_type', 'password');
+ body.set('username', '+989353989651');
+ body.set('password', '123@Qweasd');
+ body.set('client_id', 'harmony_identity');
+ body.set('scope', 'openid harmony_identity profile offline_access');
+
+ const { data } = await axios.post(
+ `${AUTH_API_URL}/connect/token`,
+ body.toString(),
+ {
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ },
+ );
+ localStorage.setItem('authToken', data.access_token);
+ return data;
+};
+
export const sendEmailOtpApi = async (
payload: SendEmailOtpPayload,
): Promise => {
diff --git a/src/features/authentication/types/completionFormApiTypes.ts b/src/features/authentication/types/completionFormApiTypes.ts
index 303deca..09c8062 100644
--- a/src/features/authentication/types/completionFormApiTypes.ts
+++ b/src/features/authentication/types/completionFormApiTypes.ts
@@ -1,3 +1,15 @@
+export interface TokenRequestPayload {
+ grant_type: 'password';
+ username: string;
+ password: string;
+ client_id: string;
+ scope: string;
+}
+
+export interface TokenApiResponse {
+ access_token: string;
+}
+
export interface GenericApiResponse {
success: boolean;
message: string;
diff --git a/src/features/authentication/types/settingForm.ts b/src/features/authentication/types/settingForm.ts
index eb9453f..a8d31cd 100644
--- a/src/features/authentication/types/settingForm.ts
+++ b/src/features/authentication/types/settingForm.ts
@@ -1,11 +1,3 @@
-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;
@@ -44,30 +36,3 @@ export interface PasswordSectionProps {
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/authorization/api/userCompletion.ts b/src/features/authorization/api/userCompletion.ts
new file mode 100644
index 0000000..d9fcf26
--- /dev/null
+++ b/src/features/authorization/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/authorization/components/AuthenticationSteps/OtpVerifyForm.tsx b/src/features/authorization/components/AuthenticationSteps/OtpVerifyForm.tsx
index 53a207b..7471568 100644
--- a/src/features/authorization/components/AuthenticationSteps/OtpVerifyForm.tsx
+++ b/src/features/authorization/components/AuthenticationSteps/OtpVerifyForm.tsx
@@ -1,7 +1,7 @@
import { useTranslation } from 'react-i18next';
import { Box, Button, Stack, Typography } from '@mui/material';
import { Edit2 } from 'iconsax-react';
-import DigitInput from '@/components/components/DigitsInput';
+import DigitInput from '@/components/DigitsInput';
import type { AuthMode, AuthType } from '../../types/authTypes';
import { useEffect, useState } from 'react';
import { AuthenticationCard } from '../AuthenticationCard';
diff --git a/src/features/authorization/components/AuthenticationSteps/VerifyPhoneNumber.tsx b/src/features/authorization/components/AuthenticationSteps/VerifyPhoneNumber.tsx
index 5db38ac..3af34fd 100644
--- a/src/features/authorization/components/AuthenticationSteps/VerifyPhoneNumber.tsx
+++ b/src/features/authorization/components/AuthenticationSteps/VerifyPhoneNumber.tsx
@@ -1,7 +1,7 @@
import { useTranslation } from 'react-i18next';
import { Box, Button, Stack, Typography } from '@mui/material';
import { Edit2 } from 'iconsax-react';
-import DigitInput from '@/components/components/DigitsInput';
+import DigitInput from '@/components/DigitsInput';
import { useEffect, useState } from 'react';
import { AuthenticationCard } from '../AuthenticationCard';
import type { ConfirmSmsOtpRequest } from '../../types/userTypes';
diff --git a/src/features/authorization/components/CountryCodeSelector.tsx b/src/features/authorization/components/CountryCodeSelector.tsx
index 7fcd19b..575c839 100644
--- a/src/features/authorization/components/CountryCodeSelector.tsx
+++ b/src/features/authorization/components/CountryCodeSelector.tsx
@@ -13,7 +13,7 @@ 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 '../../../countries';
+import { countries, type Country } from '../../../data/countries';
import type { CountryCode } from '@/types/commonTypes';
import { Icon } from '@rkheftan/harmony-ui';
interface CountryCodeSelectorProps {
diff --git a/src/features/authorization/components/ForgetPassword/ChangePassword.tsx b/src/features/authorization/components/ForgetPassword/ChangePassword.tsx
index 024db54..70cc6d2 100644
--- a/src/features/authorization/components/ForgetPassword/ChangePassword.tsx
+++ b/src/features/authorization/components/ForgetPassword/ChangePassword.tsx
@@ -8,10 +8,8 @@ import {
Stack,
TextField,
Typography,
- useTheme,
} from '@mui/material';
import { useTranslation } from 'react-i18next';
-import { Toast } from '@/components/Toast';
import { containsNumber } from '@/utils/regexes/containsNumber';
import { containsSymbol } from '@/utils/regexes/containsSymbol';
import { least8Chars } from '@/utils/regexes/least8Chars';
@@ -38,7 +36,6 @@ export const ChangePassword = ({
infoType,
countryCode,
}: ChangePasswordProps) => {
- const theme = useTheme();
const { t } = useTranslation('authentication');
const [passValue, setPassValue] = useState('');
const [confirmPassValue, setConfirmPassValue] = useState('');
diff --git a/src/features/authorization/components/ForgetPassword/ForgetPasswordOtp.tsx b/src/features/authorization/components/ForgetPassword/ForgetPasswordOtp.tsx
index a1fd5f3..55875cf 100644
--- a/src/features/authorization/components/ForgetPassword/ForgetPasswordOtp.tsx
+++ b/src/features/authorization/components/ForgetPassword/ForgetPasswordOtp.tsx
@@ -1,10 +1,9 @@
import { useTranslation } from 'react-i18next';
import { Box, Button, Stack, Typography } from '@mui/material';
import { Edit2 } from 'iconsax-react';
-import DigitInput from '@/components/components/DigitsInput';
+import DigitInput from '@/components/DigitsInput';
import type { AuthType } from '../../types/authTypes';
import { useEffect, useState } from 'react';
-import { Toast } from '@/components/Toast';
import { AuthenticationCard } from '../AuthenticationCard';
import type {
ConfirmForgetPassCodeRequest,
@@ -41,7 +40,6 @@ export function ForgetPasswordOtp({
const [canResend, setCanResend] = useState(false);
const toast = useToast();
const {
- data: sendForgetPassCodeData,
loading: sendForgetPassCodeLoading,
execute: sendForgetPassCodeCall,
} = useApi(sendForgetPassCode);
diff --git a/src/features/authorization/components/ForgetPassword/ForgettedPasswordInfo.tsx b/src/features/authorization/components/ForgetPassword/ForgettedPasswordInfo.tsx
index 997a8aa..2b3a0e0 100644
--- a/src/features/authorization/components/ForgetPassword/ForgettedPasswordInfo.tsx
+++ b/src/features/authorization/components/ForgetPassword/ForgettedPasswordInfo.tsx
@@ -9,7 +9,6 @@ import { CountryCodeSelector } from '../CountryCodeSelector';
import type { CountryCode } from '@/types/commonTypes';
import { sendForgetPassCode } from '../../api/authorizationAPI';
import type { SendForgetPassCodeRequest } from '../../types/userTypes';
-import { Toast } from '@/components/Toast';
import { isPhoneNumber } from '@/utils/regexes/isValidPhoneNumber';
import { useToast } from '@rkheftan/harmony-ui';
import { useApi } from '@/hooks/useApi';
diff --git a/src/features/authentication/components/DateOfBirth.tsx b/src/features/authorization/components/UserCompletionForm/DateOfBirth.tsx
similarity index 97%
rename from src/features/authentication/components/DateOfBirth.tsx
rename to src/features/authorization/components/UserCompletionForm/DateOfBirth.tsx
index f219589..10a2c29 100644
--- a/src/features/authentication/components/DateOfBirth.tsx
+++ b/src/features/authorization/components/UserCompletionForm/DateOfBirth.tsx
@@ -14,7 +14,7 @@ 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';
+import { type DateOfBirthProps } from '../../types/settingForm';
export default function DateOfBirth({ value, onChange }: DateOfBirthProps) {
const { t, i18n } = useTranslation('completionForm');
diff --git a/src/features/authentication/components/EmailSection.tsx b/src/features/authorization/components/UserCompletionForm/EmailSection.tsx
similarity index 98%
rename from src/features/authentication/components/EmailSection.tsx
rename to src/features/authorization/components/UserCompletionForm/EmailSection.tsx
index 022126d..341faa0 100644
--- a/src/features/authentication/components/EmailSection.tsx
+++ b/src/features/authorization/components/UserCompletionForm/EmailSection.tsx
@@ -13,7 +13,7 @@ import {
import { useTranslation } from 'react-i18next';
import { TickCircle, Edit } from 'iconsax-react';
import { Icon } from '@rkheftan/harmony-ui';
-import { type EmailSectionProps } from '../types/settingForm';
+import { type EmailSectionProps } from '../../types/settingForm';
export function EmailSection({
showEmail,
diff --git a/src/features/authentication/components/PasswordSection.tsx b/src/features/authorization/components/UserCompletionForm/PasswordSection.tsx
similarity index 99%
rename from src/features/authentication/components/PasswordSection.tsx
rename to src/features/authorization/components/UserCompletionForm/PasswordSection.tsx
index f8da4e9..7ef1d26 100644
--- a/src/features/authentication/components/PasswordSection.tsx
+++ b/src/features/authorization/components/UserCompletionForm/PasswordSection.tsx
@@ -12,7 +12,7 @@ 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';
+import { type PasswordSectionProps } from '../../types/settingForm';
export function PasswordSection({
showPasswordSection,
diff --git a/src/features/authentication/components/PasswordValidation.tsx b/src/features/authorization/components/UserCompletionForm/PasswordValidation.tsx
similarity index 91%
rename from src/features/authentication/components/PasswordValidation.tsx
rename to src/features/authorization/components/UserCompletionForm/PasswordValidation.tsx
index 9f482d7..4667f1d 100644
--- a/src/features/authentication/components/PasswordValidation.tsx
+++ b/src/features/authorization/components/UserCompletionForm/PasswordValidation.tsx
@@ -1,7 +1,7 @@
import { Box, Typography } from '@mui/material';
import { TickCircle } from 'iconsax-react';
import { Icon } from '@rkheftan/harmony-ui';
-import { type ValidationItemProps } from '../types/settingForm';
+import { type ValidationItemProps } from '../../types/settingForm';
export function PasswordValidationItem({
isValid,
diff --git a/src/features/authentication/components/PersonalInfoFields.tsx b/src/features/authorization/components/UserCompletionForm/PersonalInfoFields.tsx
similarity index 87%
rename from src/features/authentication/components/PersonalInfoFields.tsx
rename to src/features/authorization/components/UserCompletionForm/PersonalInfoFields.tsx
index 1f1d8e1..b33ad35 100644
--- a/src/features/authentication/components/PersonalInfoFields.tsx
+++ b/src/features/authorization/components/UserCompletionForm/PersonalInfoFields.tsx
@@ -11,11 +11,11 @@ import {
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 { countries } from '@/data/countries';
import { Icon } from '@rkheftan/harmony-ui';
-import { type PersonalInfoFieldsProps } from '../types/settingForm';
-import { Gender } from '../types/settingForm';
+import { type PersonalInfoFieldsProps } from '../../types/settingForm';
+import { Gender } from '../../types/settingForm';
+import ReactCountryFlag from 'react-country-flag';
export function PersonalInfoFields({
firstName,
@@ -148,7 +148,17 @@ export function PersonalInfoFields({
onChange={(_, newValue) => setCountry(newValue?.code || '')}
renderOption={(props, option) => (
-
+
{option.label}
)}
diff --git a/src/features/authentication/components/SubmitSection.tsx b/src/features/authorization/components/UserCompletionForm/SubmitSection.tsx
similarity index 97%
rename from src/features/authentication/components/SubmitSection.tsx
rename to src/features/authorization/components/UserCompletionForm/SubmitSection.tsx
index b1c8c07..448078c 100644
--- a/src/features/authentication/components/SubmitSection.tsx
+++ b/src/features/authorization/components/UserCompletionForm/SubmitSection.tsx
@@ -9,7 +9,7 @@ import {
DialogContent,
} from '@mui/material';
import { useTranslation } from 'react-i18next';
-import { type SubmitProps } from '../types/settingForm';
+import { type SubmitProps } from '../../types/settingForm';
export function SubmitSection({ onSubmit, loading, error }: SubmitProps) {
const { t, i18n } = useTranslation('completionForm');
diff --git a/src/features/authorization/index.ts b/src/features/authorization/index.ts
index e16de18..7f380b6 100644
--- a/src/features/authorization/index.ts
+++ b/src/features/authorization/index.ts
@@ -1,2 +1,3 @@
export * from './routes/AuthenticationPage';
export * from './routes/ForgetPasswordPage';
+export * from './routes/UserCompletionPage';
diff --git a/src/features/authorization/routes/AuthenticationPage.tsx b/src/features/authorization/routes/AuthenticationPage.tsx
index 47917cf..8104bbc 100644
--- a/src/features/authorization/routes/AuthenticationPage.tsx
+++ b/src/features/authorization/routes/AuthenticationPage.tsx
@@ -1,4 +1,4 @@
-import { FlexBox } from '@/components/components/common/FlexBox';
+import { FlexBox } from '@/components/common/FlexBox';
import Logo from '@/components/Logo';
import { AuthenticationSteps } from '../components/AuthenticationSteps/AuthenticationSteps';
diff --git a/src/features/authorization/routes/ForgetPasswordPage.tsx b/src/features/authorization/routes/ForgetPasswordPage.tsx
index f54d961..74c971f 100644
--- a/src/features/authorization/routes/ForgetPasswordPage.tsx
+++ b/src/features/authorization/routes/ForgetPasswordPage.tsx
@@ -1,4 +1,4 @@
-import { FlexBox } from '@/components/components/common/FlexBox';
+import { FlexBox } from '@/components/common/FlexBox';
import Logo from '@/components/Logo';
import { ForgetPasswordContainer } from '../components/ForgetPassword/ForgetPasswordContainer';
diff --git a/src/features/authentication/components/UserCompletionForm.tsx b/src/features/authorization/routes/UserCompletionPage.tsx
similarity index 90%
rename from src/features/authentication/components/UserCompletionForm.tsx
rename to src/features/authorization/routes/UserCompletionPage.tsx
index 5d220ef..8e86c90 100644
--- a/src/features/authentication/components/UserCompletionForm.tsx
+++ b/src/features/authorization/routes/UserCompletionPage.tsx
@@ -2,12 +2,11 @@ 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 { PersonalInfoFields } from '../components/UserCompletionForm/PersonalInfoFields';
+import { PasswordSection } from '../components/UserCompletionForm/PasswordSection';
+import { EmailSection } from '../components/UserCompletionForm/EmailSection';
+import { SubmitSection } from '../components/UserCompletionForm/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';
@@ -23,8 +22,13 @@ import {
type CompleteUserInfoPayload,
} from '../types/completionFormApiTypes';
import { type ApiResponse } from '@/types/apiResponse';
+import { containsSymbol } from '@/utils/regexes/containsSymbol';
+import { least8Chars } from '@/utils/regexes/least8Chars';
+import { containsNumber } from '@/utils/regexes/containsNumber';
+import { hasUpperAndLowerLetter } from '@/utils/regexes/hasUpperAndLowerLetter';
+import { isEmail } from '@/utils/regexes/isEmail';
-export function UserCompletionForm() {
+export function UserCompletionPage() {
const { t } = useTranslation('completionForm');
const showToast = useToast();
@@ -49,15 +53,15 @@ export function UserCompletionForm() {
const [emailVerified, setEmailVerified] = useState(false);
const [showPasswordValidations, setShowPasswordValidations] = useState(false);
- const {
- hasNumber,
- hasMinLength,
- hasUpperAndLower,
- hasSpecialChar,
- validPassword,
- correctEmail,
- } = regex(password, email);
+ const hasNumber = containsNumber(password);
+ const hasMinLength = least8Chars(password);
+ const hasUpperAndLower = hasUpperAndLowerLetter(password);
+ const hasSpecialChar = containsSymbol(password);
+ const validPassword =
+ hasNumber && hasMinLength && hasUpperAndLower && hasSpecialChar;
+
const matchPassword = password === confirmPassword;
+ const correctEmail = isEmail(email);
const { execute: sendCode, data: sendCodeData } = useApi(
async (payload: SendEmailOtpPayload) => {
diff --git a/src/features/authorization/types/completionFormApiTypes.ts b/src/features/authorization/types/completionFormApiTypes.ts
new file mode 100644
index 0000000..303deca
--- /dev/null
+++ b/src/features/authorization/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/authorization/types/settingForm.ts b/src/features/authorization/types/settingForm.ts
new file mode 100644
index 0000000..eb9453f
--- /dev/null
+++ b/src/features/authorization/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
index 3e86b44..4cb45a7 100644
--- a/src/features/profile/api/settingsApi.ts
+++ b/src/features/profile/api/settingsApi.ts
@@ -14,160 +14,93 @@ import {
} 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 fetchProfile() {
+ return apiClient.post('/Profile/GetProfile');
}
-export async function saveProfile(payload?: {
+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(
+}) {
+ return 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(
+export async function sendVerificationCode(payload: { phoneNumber: string }) {
+ return apiClient.post(
'/Profile/SendVerfiyPhoneNumberCode',
payload,
);
- return { data: res.data };
}
-export async function confirmPhoneNumberCode(payload?: {
+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(
+}) {
+ return 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(
+export async function changePhoneNumber(payload: { phoneNumber: string }) {
+ return apiClient.post(
'/Profile/ChangePhoneNumber',
payload,
);
- return { data: res.data };
}
-export async function saveSettings(payload?: {
+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(
+}) {
+ return 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(
+export async function deleteSessions(payload: { keys: string[] }) {
+ return 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 addPassword(payload: { password: string }) {
+ return apiClient.post('/Profile/AddPassword', payload);
}
-export async function changePassword(payload?: {
+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(
+}) {
+ return 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(
+export async function sendEmailCode(payload: { email: string }) {
+ return apiClient.post(
'Profile/SendEmailChangeCode',
payload,
);
- return { data: res.data };
}
-export async function confirmEmailCode(payload?: {
+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(
+}) {
+ return 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 };
+export async function changeEmail(payload: { email: string }) {
+ return apiClient.post('Profile/ChangeEmail', payload);
}
diff --git a/src/features/profile/components/CountryCodeSelector.tsx b/src/features/profile/components/CountryCodeSelector.tsx
index 8a82727..fd948db 100644
--- a/src/features/profile/components/CountryCodeSelector.tsx
+++ b/src/features/profile/components/CountryCodeSelector.tsx
@@ -13,7 +13,7 @@ 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';
+import { countries, type Country } from '@/data/countries';
interface CountryCodeSelectorProps {
show: boolean;
diff --git a/src/features/profile/components/security/PasswordSecurity.tsx b/src/features/profile/components/security/PasswordSecurity.tsx
index a847ee9..9d5093b 100644
--- a/src/features/profile/components/security/PasswordSecurity.tsx
+++ b/src/features/profile/components/security/PasswordSecurity.tsx
@@ -11,13 +11,16 @@ 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 { useApi } from '@/hooks/useApi';
import {
addPassword,
changePassword,
fetchProfile,
} from '../../api/settingsApi';
+import { containsNumber } from '@/utils/regexes/containsNumber';
+import { least8Chars } from '@/utils/regexes/least8Chars';
+import { hasUpperAndLowerLetter } from '@/utils/regexes/hasUpperAndLowerLetter';
+import { containsSymbol } from '@/utils/regexes/containsSymbol';
export function PasswordSecurity() {
const theme = useTheme();
@@ -29,36 +32,36 @@ export function PasswordSecurity() {
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 hasNumber = containsNumber(password);
+ const hasMinLength = least8Chars(password);
+ const hasUpperAndLower = hasUpperAndLowerLetter(password);
+ const hasSpecialChar = containsSymbol(password);
+ const validPassword =
+ hasNumber && hasMinLength && hasUpperAndLower && hasSpecialChar;
+
const matchPassword = password === confirmPassword;
const {
data: profileData,
loading: isFetching,
error: fetchError,
- execute: executeFetchProfile,
- } = useManualApi(fetchProfile);
+ } = useApi(fetchProfile, { immediate: true });
const {
data: addData,
loading: isAdding,
error: addError,
execute: executeAddPassword,
- } = useManualApi(addPassword);
+ } = useApi(addPassword);
const {
data: changeData,
loading: isChanging,
error: changeError,
execute: executeChangePassword,
- } = useManualApi(changePassword);
-
- useEffect(() => {
- executeFetchProfile();
- }, []);
+ } = useApi(changePassword);
useEffect(() => {
if (profileData?.success) {
@@ -76,7 +79,6 @@ export function PasswordSecurity() {
useEffect(() => {
if (addData?.success || changeData?.success) {
- setShowPasswordAlert(true);
if (!changePasswordUI) {
setChangePasswordUI(true);
}
@@ -90,14 +92,14 @@ export function PasswordSecurity() {
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
- const handlePasswordSubmit = () => {
+ const handlePasswordSubmit = async () => {
if (changePasswordUI) {
- executeChangePassword({
+ await executeChangePassword({
oldPassword: currentPassword,
newPassword: password,
});
} else {
- executeAddPassword({ password });
+ await executeAddPassword({ password });
}
};
diff --git a/src/features/profile/components/security/RecentLogins.tsx b/src/features/profile/components/security/RecentLogins.tsx
index a285229..fab5a14 100644
--- a/src/features/profile/components/security/RecentLogins.tsx
+++ b/src/features/profile/components/security/RecentLogins.tsx
@@ -11,7 +11,7 @@ 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 { useApi } from '@/hooks/useApi';
import { formatDate } from '@/utils/formatSessionDate';
export function RecentLogins() {
@@ -24,12 +24,7 @@ export function RecentLogins() {
data: profileData,
loading: isLoading,
error: fetchError,
- execute: executeFetchProfile,
- } = useManualApi(fetchProfile);
-
- useEffect(() => {
- executeFetchProfile();
- }, [i18n.language]);
+ } = useApi(fetchProfile, { immediate: true });
useEffect(() => {
if (profileData?.success && Array.isArray(profileData.loginLogs)) {
diff --git a/src/features/profile/components/userInformation/PersonalInformation.tsx b/src/features/profile/components/userInformation/PersonalInformation.tsx
index d89b4c1..92a6daa 100644
--- a/src/features/profile/components/userInformation/PersonalInformation.tsx
+++ b/src/features/profile/components/userInformation/PersonalInformation.tsx
@@ -6,7 +6,7 @@ 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 { useApi } from '@/hooks/useApi';
import { Gender, type InfoRowData } from '../../types/settingsType';
import { fetchProfile, saveProfile } from '../../api/settingsApi';
@@ -27,19 +27,14 @@ export function PersonalInformation() {
data: profileData,
loading: isLoadingProfile,
error: fetchProfileError,
- execute: executeFetchProfile,
- } = useManualApi(fetchProfile);
+ } = useApi(fetchProfile, { immediate: true });
const {
data: saveData,
loading: isSavingProfile,
error: saveProfileError,
execute: executeSaveProfile,
- } = useManualApi(saveProfile);
-
- useEffect(() => {
- executeFetchProfile();
- }, []);
+ } = useApi(saveProfile);
useEffect(() => {
if (profileData?.success) {
diff --git a/src/features/profile/components/userInformation/PhoneNumber.tsx b/src/features/profile/components/userInformation/PhoneNumber.tsx
index 148e937..5133179 100644
--- a/src/features/profile/components/userInformation/PhoneNumber.tsx
+++ b/src/features/profile/components/userInformation/PhoneNumber.tsx
@@ -8,7 +8,7 @@ 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 { useApi } from '@/hooks/useApi';
import {
fetchProfile,
sendVerificationCode,
@@ -39,35 +39,28 @@ export function PhoneNumber() {
data: profileData,
loading: isLoading,
error: fetchError,
- execute: executeFetchProfile,
- } = useManualApi(fetchProfile);
+ } = useApi(fetchProfile, { immediate: true });
const {
data: sendCodeData,
loading: isSendingCode,
error: sendCodeError,
execute: executeSendCode,
- } = useManualApi(sendVerificationCode);
+ } = useApi(sendVerificationCode);
const {
data: confirmData,
loading: isVerifying,
error: confirmError,
execute: executeConfirmCode,
- } = useManualApi(confirmPhoneNumberCode);
+ } = useApi(confirmPhoneNumberCode);
const {
data: changePhoneData,
loading: isChangingPhone,
error: changePhoneError,
execute: executeChangePhone,
- } = useManualApi(changePhoneNumber);
-
- useEffect(() => {
- if (!isEditing) {
- executeFetchProfile();
- }
- }, [isEditing]);
+ } = useApi(changePhoneNumber);
useEffect(() => {
if (profileData?.success && profileData.phoneNumber) {
diff --git a/src/features/profile/components/userInformation/SocialMedia.tsx b/src/features/profile/components/userInformation/SocialMedia.tsx
index 98dc1ab..8c150f4 100644
--- a/src/features/profile/components/userInformation/SocialMedia.tsx
+++ b/src/features/profile/components/userInformation/SocialMedia.tsx
@@ -8,7 +8,7 @@ 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 { useApi } from '@/hooks/useApi';
import {
fetchProfile,
sendEmailCode,
@@ -33,29 +33,28 @@ export function SocialMedia() {
data: profileData,
loading: isFetching,
error: fetchError,
- execute: executeFetchProfile,
- } = useManualApi(fetchProfile);
+ } = useApi(fetchProfile, { immediate: true });
const {
data: sendCodeData,
loading: isSendingCode,
error: sendCodeError,
execute: executeSendCode,
- } = useManualApi(sendEmailCode);
+ } = useApi(sendEmailCode);
const {
data: confirmData,
loading: isConfirming,
error: confirmError,
execute: executeConfirmCode,
- } = useManualApi(confirmEmailCode);
+ } = useApi(confirmEmailCode);
const {
data: changeEmailData,
loading: isChangingEmail,
error: changeEmailError,
execute: executeChangeEmail,
- } = useManualApi(changeEmail);
+ } = useApi(changeEmail);
const fullScreen = useMediaQuery((theme: Theme) =>
theme.breakpoints.down('sm'),
@@ -68,10 +67,6 @@ export function SocialMedia() {
| 'lg'
| 'xl';
- useEffect(() => {
- executeFetchProfile();
- }, []);
-
useEffect(() => {
if (profileData?.success && profileData.email) {
const { email } = profileData;
diff --git a/src/features/profile/components/userInformation/personalInformation/InfoRowDisplay.tsx b/src/features/profile/components/userInformation/personalInformation/InfoRowDisplay.tsx
index 9f95ceb..9bb46a2 100644
--- a/src/features/profile/components/userInformation/personalInformation/InfoRowDisplay.tsx
+++ b/src/features/profile/components/userInformation/personalInformation/InfoRowDisplay.tsx
@@ -1,9 +1,9 @@
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';
+import ReactCountryFlag from 'react-country-flag';
export function InfoRowDisplay({
data,
@@ -72,7 +72,17 @@ export function InfoRowDisplay({
{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
index 3c7f9c6..f05dfd6 100644
--- a/src/features/profile/components/userInformation/personalInformation/InfoRowEdit.tsx
+++ b/src/features/profile/components/userInformation/personalInformation/InfoRowEdit.tsx
@@ -8,10 +8,10 @@ import {
Autocomplete,
} from '@mui/material';
import { useTranslation } from 'react-i18next';
-import { countries } from '@/features/profile/data/countries';
-import { CountryFlag } from '@/components/CountryFlag';
+import { countries } from '@/data/countries';
import { Gender } from '@/features/profile/types/settingsType';
import { type InfoRowEditProps } from '@/features/profile/types/settingsType';
+import ReactCountryFlag from 'react-country-flag';
export function InfoRowEdit({ data, setData }: InfoRowEditProps) {
const { t } = useTranslation(['countries', 'setting']);
@@ -101,7 +101,17 @@ export function InfoRowEdit({ data, setData }: InfoRowEditProps) {
}
renderOption={(props, option) => (
-
+
{option.label}
)}
diff --git a/src/features/profile/data/countries.ts b/src/features/profile/data/countries.ts
deleted file mode 100644
index 2b05f9e..0000000
--- a/src/features/profile/data/countries.ts
+++ /dev/null
@@ -1,271 +0,0 @@
-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/index.ts b/src/features/profile/index.ts
new file mode 100644
index 0000000..d3522b4
--- /dev/null
+++ b/src/features/profile/index.ts
@@ -0,0 +1,4 @@
+export * from './routes/UserInformationPage';
+export * from './routes/ActiveDevicesPage';
+export * from './routes/SettingPage';
+export * from './routes/SecurityPage';
diff --git a/src/features/profile/components/activeDevices/ActiveDevices.tsx b/src/features/profile/routes/ActiveDevicesPage.tsx
similarity index 94%
rename from src/features/profile/components/activeDevices/ActiveDevices.tsx
rename to src/features/profile/routes/ActiveDevicesPage.tsx
index 47cce7e..d6fd3d9 100644
--- a/src/features/profile/components/activeDevices/ActiveDevices.tsx
+++ b/src/features/profile/routes/ActiveDevicesPage.tsx
@@ -10,15 +10,15 @@ import {
import { useTranslation } from 'react-i18next';
import { DeviceMessage, Logout } from 'iconsax-react';
import { CardContainer } from '@/components/CardContainer';
-import { PageWrapper } from '../PageWrapper';
+import { PageWrapper } from '../components/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 { fetchProfile, deleteSessions } from '../api/settingsApi';
+import { type ApiSession } from '../types/settingsApiType';
+import { useApi } from '@/hooks/useApi';
import { formatDate } from '@/utils/formatSessionDate';
-import { type Device } from '../../types/settingsType';
+import { type Device } from '../types/settingsType';
-export function ActiveDevices() {
+export function ActiveDevicesPage() {
const { t, i18n } = useTranslation('setting');
const [devices, setDevices] = useState([]);
const [loadingDeleteIds, setLoadingDeleteIds] = useState([]);
@@ -29,18 +29,13 @@ export function ActiveDevices() {
data: profileData,
loading: isLoading,
error: fetchError,
- execute: executeFetchProfile,
- } = useManualApi(fetchProfile);
+ } = useApi(fetchProfile, { immediate: true });
const {
data: terminateData,
loading: isTerminating,
execute: executeTerminateAll,
- } = useManualApi(deleteSessions);
-
- useEffect(() => {
- executeFetchProfile();
- }, [i18n.language]);
+ } = useApi(deleteSessions);
useEffect(() => {
if (profileData?.success && profileData.activeSessions) {
diff --git a/src/features/profile/routes/SecurityPage.tsx b/src/features/profile/routes/SecurityPage.tsx
new file mode 100644
index 0000000..5e4153b
--- /dev/null
+++ b/src/features/profile/routes/SecurityPage.tsx
@@ -0,0 +1,16 @@
+import { PasswordSecurity } from '../components/security/PasswordSecurity';
+import { RecentLogins } from '../components/security/RecentLogins';
+import { Box } from '@mui/material';
+
+export function SecurityPage() {
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/features/profile/components/setting/Setting.tsx b/src/features/profile/routes/SettingPage.tsx
similarity index 94%
rename from src/features/profile/components/setting/Setting.tsx
rename to src/features/profile/routes/SettingPage.tsx
index 1037286..726d31d 100644
--- a/src/features/profile/components/setting/Setting.tsx
+++ b/src/features/profile/routes/SettingPage.tsx
@@ -11,11 +11,11 @@ import {
import { CardContainer } from '@/components/CardContainer';
import { useTranslation } from 'react-i18next';
import { ThemeToggleButton } from '@/components/ThemToggle';
-import { PageWrapper } from '../PageWrapper';
+import { PageWrapper } from '../components/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';
+import { useApi } from '@/hooks/useApi';
+import { fetchProfile, saveSettings } from '../api/settingsApi';
type ThemeMode = 'light' | 'dark';
type CalendarType = 'christian' | 'solar' | 'lunar';
@@ -39,7 +39,7 @@ const calendarOptions: { key: CalendarType; apiValue: number }[] = [
const themeApiMap: Record = { light: 1, dark: 2 };
-export function Setting() {
+export function SettingPage() {
const { t, i18n } = useTranslation(['setting']);
const { mode, setMode } = useColorScheme();
@@ -56,19 +56,14 @@ export function Setting() {
data: profileData,
loading: isFetching,
error: fetchError,
- execute: executeFetchProfile,
- } = useManualApi(fetchProfile);
+ } = useApi(fetchProfile, { immediate: true });
const {
data: saveData,
loading: isSaving,
error: saveError,
execute: executeSaveSettings,
- } = useManualApi(saveSettings);
-
- useEffect(() => {
- executeFetchProfile();
- }, []);
+ } = useApi(saveSettings);
useEffect(() => {
if (profileData?.success && profileData.userSettings) {
@@ -254,11 +249,9 @@ export function Setting() {
o.label}
- value={
- languageOptions.find(
- (o) => o.code === draftSettings.language,
- ) // ✅ FIX: Removed '|| null'
- }
+ value={languageOptions.find(
+ (o) => o.code === draftSettings.language,
+ )}
onChange={(_, v) =>
v &&
setDraftSettings((prev) => ({
diff --git a/src/features/profile/routes/UserInformationPage.tsx b/src/features/profile/routes/UserInformationPage.tsx
new file mode 100644
index 0000000..4a9d275
--- /dev/null
+++ b/src/features/profile/routes/UserInformationPage.tsx
@@ -0,0 +1,20 @@
+import { PersonalInformation } from '../components/userInformation/PersonalInformation';
+import { PhoneNumber } from '../components/userInformation/PhoneNumber';
+import { SocialMedia } from '../components/userInformation/SocialMedia';
+import { Box } from '@mui/material';
+
+export function UserInformationPage() {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/hooks/useApi.ts b/src/hooks/useApi.ts
index ff98bd0..bd9811a 100644
--- a/src/hooks/useApi.ts
+++ b/src/hooks/useApi.ts
@@ -1,4 +1,4 @@
-// FIXME: all the responses should extend this interface
+// 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';
@@ -51,8 +51,7 @@ export function useApi(
}
},
[apiFunction, navigate, t, toast],
- )
-
+ );
// If the 'immediate' option is true, execute the function on mount
useEffect(() => {
diff --git a/src/lib/identityClient.ts b/src/lib/identityClient.ts
deleted file mode 100644
index 4282594..0000000
--- a/src/lib/identityClient.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import axios from 'axios';
-
-const identityClient = axios.create({
- // Define the base URL for all API requests
- baseURL: import.meta.env.VITE_IDENTITY_URL,
-
- // Set a timeout for requests (e.g., 10 seconds)
- timeout: 10000,
-
- // Set default headers
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
- },
-});
diff --git a/src/routes/config.tsx b/src/routes/config.tsx
index 5340c92..1aa145c 100644
--- a/src/routes/config.tsx
+++ b/src/routes/config.tsx
@@ -1,8 +1,15 @@
-import { Layout } from '@/components/Layout/Layout';
+import { Layout } from '@/components';
import {
AuthenticationPage,
ForgetPasswordPage,
+ UserCompletionPage,
} from '@/features/authorization';
+import {
+ ActiveDevicesPage,
+ SecurityPage,
+ SettingPage,
+ UserInformationPage,
+} from '@/features/profile';
import {
Calendar,
Devices,
@@ -39,26 +46,22 @@ export const appRoutes: RouteConfig[] = [
{
path: '/login',
element: ,
- authorize: l,
},
+ { path: '/signup', element: , authorize: true },
{
path: '/forget-password',
element: ,
- authorize: false,
- },
- {
- path: '/',
- element: ,
},
+
{
path: '/setting',
element: ,
+ authorize: true,
children: [
- // TODO: add route component to each route
{
path: '/setting/profile',
+ element: ,
navConfig: {
- // Profile component
title: 'side.account',
icon: ProfileCircle,
},
@@ -88,7 +91,7 @@ export const appRoutes: RouteConfig[] = [
},
{
path: '/setting/security',
- // security component
+ element: ,
navConfig: {
title: 'side.security',
icon: Shield,
@@ -119,7 +122,7 @@ export const appRoutes: RouteConfig[] = [
},
{
path: '/setting/active-sessions',
- // active session component
+ element: ,
navConfig: {
title: 'side.activeSessions',
icon: Calendar,
@@ -127,7 +130,7 @@ export const appRoutes: RouteConfig[] = [
},
{
path: '/setting/preferences',
- // setting component
+ element: ,
navConfig: {
title: 'side.setting',
icon: Setting,
diff --git a/src/utils/formatSessionDate.tsx b/src/utils/formatSessionDate.ts
similarity index 100%
rename from src/utils/formatSessionDate.tsx
rename to src/utils/formatSessionDate.ts
diff --git a/src/utils/persianDigit.tsx b/src/utils/persianDigit.ts
similarity index 100%
rename from src/utils/persianDigit.tsx
rename to src/utils/persianDigit.ts
diff --git a/src/utils/regex.ts b/src/utils/regex.ts
deleted file mode 100644
index a4c68f4..0000000
--- a/src/utils/regex.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-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/src/utils/regexes/containsNumber.tsx b/src/utils/regexes/containsNumber.ts
similarity index 100%
rename from src/utils/regexes/containsNumber.tsx
rename to src/utils/regexes/containsNumber.ts
diff --git a/src/utils/regexes/containsSymbol.tsx b/src/utils/regexes/containsSymbol.ts
similarity index 100%
rename from src/utils/regexes/containsSymbol.tsx
rename to src/utils/regexes/containsSymbol.ts
diff --git a/src/utils/regexes/hasUpperAndLowerLetter.tsx b/src/utils/regexes/hasUpperAndLowerLetter.ts
similarity index 100%
rename from src/utils/regexes/hasUpperAndLowerLetter.tsx
rename to src/utils/regexes/hasUpperAndLowerLetter.ts
diff --git a/src/utils/regexes/isEmail.tsx b/src/utils/regexes/isEmail.ts
similarity index 100%
rename from src/utils/regexes/isEmail.tsx
rename to src/utils/regexes/isEmail.ts
diff --git a/src/utils/regexes/isValidPhoneNumber.tsx b/src/utils/regexes/isValidPhoneNumber.ts
similarity index 100%
rename from src/utils/regexes/isValidPhoneNumber.tsx
rename to src/utils/regexes/isValidPhoneNumber.ts
diff --git a/src/utils/regexes/least8Chars.tsx b/src/utils/regexes/least8Chars.ts
similarity index 100%
rename from src/utils/regexes/least8Chars.tsx
rename to src/utils/regexes/least8Chars.ts