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