diff --git a/package-lock.json b/package-lock.json
index 9e9436f..d528328 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,6 +21,7 @@
"react-country-flag": "^3.1.0",
"react-dom": "^19.1.0",
"react-i18next": "^15.6.0",
+ "react-router": "^7.8.0",
"react-virtuoso": "^4.13.0",
"stylis": "^4.3.6",
"stylis-plugin-rtl": "^2.1.1"
@@ -2404,6 +2405,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/cosmiconfig": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@@ -3794,6 +3804,28 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-router": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.0.tgz",
+ "integrity": "sha512-r15M3+LHKgM4SOapNmsH3smAizWds1vJ0Z9C4mWaKnT9/wD7+d/0jYcj6LmOvonkrO4Rgdyp4KQ/29gWN2i1eg==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
@@ -3940,6 +3972,12 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
diff --git a/package.json b/package.json
index 91e1810..a876532 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
"react-country-flag": "^3.1.0",
"react-dom": "^19.1.0",
"react-i18next": "^15.6.0",
+ "react-router": "^7.8.0",
"react-virtuoso": "^4.13.0",
"stylis": "^4.3.6",
"stylis-plugin-rtl": "^2.1.1"
diff --git a/src/App.tsx b/src/App.tsx
index 700db76..f1f5aa2 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -2,13 +2,20 @@ import { CssBaseline } from '@mui/material';
import './App.css';
import { LanguageManager } from './components/LanguageManager';
import { AuthenticationPage } from './features/authorization/routes/AuthenticationPage';
+import { BrowserRouter, Navigate, Route, Routes } from 'react-router';
function App() {
return (
<>
-
+
+
+ } />
+ } />
+
+
+ ,
>
);
}
diff --git a/src/features/authorization/api/authorizationAPI.ts b/src/features/authorization/api/authorizationAPI.ts
index 990924b..290a736 100644
--- a/src/features/authorization/api/authorizationAPI.ts
+++ b/src/features/authorization/api/authorizationAPI.ts
@@ -1,6 +1,7 @@
import type { ApiResponse } from '@/types/apiResponse';
import type { FetchPromise } from '@/types/fetchPromise';
import type {
+ CompleteUserInformationRequest,
ConfirmEmailOtpRequest,
ConfirmForgetPassCodeRequest,
ConfirmOtpResponse,
@@ -18,7 +19,7 @@ import type {
SendSmsOtpRequest,
} from '../types/userTypes';
-const API_URL = 'https://account.business-harmony.com/api/';
+const API_URL = 'https://account.business-harmony.com/api';
export const fetchRequest = (
url: string,
@@ -91,6 +92,12 @@ export const loginOrSignUpWithGoogle = async (
);
};
+export const completeUserInformation = async (
+ body: CompleteUserInformationRequest,
+) => {
+ return fetchRequest('User/CompleteUserInformation', body);
+};
+
export const logOut = async () => {
return fetchRequest('User/LogOut', {});
};
diff --git a/src/features/authorization/components/AuthenticationSteps/AuthenticationSteps.tsx b/src/features/authorization/components/AuthenticationSteps/AuthenticationSteps.tsx
index c9c951a..ccfc6c1 100644
--- a/src/features/authorization/components/AuthenticationSteps/AuthenticationSteps.tsx
+++ b/src/features/authorization/components/AuthenticationSteps/AuthenticationSteps.tsx
@@ -5,61 +5,79 @@ import { OtpVerifyForm } from './OtpVerifyForm';
import { isNumeric } from '@/utils/regexes/isNumeric';
import { CompleteSignUp } from './CompleteSignUp';
import { EnterPasswordForm } from './EnterPasswordForm';
+import { getUserStatusByPhoneNumberOrEmail } from '../../api/authorizationAPI';
+import { UserStatus } from '../../types/userTypes';
+import type { CountryCode } from '@/types/commonTypes';
+import { VerifyPhoneNumber } from './VerifyPhoneNumber';
export const AuthenticationSteps = (): JSX.Element => {
const [authMode, setAuthMode] = useState('register');
const [authType, setAuthType] = useState('phone');
const [currentStep, setCurrentStep] = useState<
- | 'emailOrPassword'
+ | 'emailOrPhone'
| 'verify'
| 'enterPassword'
| 'addPhoneNumber'
| 'addedPhoneNumberVerify'
- >('emailOrPassword');
+ >('emailOrPhone');
const [loginRegisterValue, setLoginRegisterValue] = useState('');
+ const [countryCode, setCountryCode] = useState('+98');
const [addedPhoneNumberValue, setAddedPhoneNumberValue] =
useState('');
- const handleLoginRegister = (value: string) => {
- setLoginRegisterValue(value);
+ const handleLoginRegister = (value: string, userStatus: UserStatus) => {
setAuthType(isNumeric(value) ? 'phone' : 'email');
- // TODO: after api: send to password if it has account and has password
- if (true) {
- setCurrentStep('enterPassword');
- } else {
- setCurrentStep('verify');
+ switch (userStatus) {
+ case UserStatus.NotRegistered:
+ setAuthMode('register');
+ setCurrentStep('verify');
+ break;
+
+ case UserStatus.RegisteredWithoutPassword:
+ setAuthMode('login');
+ setCurrentStep('verify');
+
+ break;
+
+ case UserStatus.RegisteredWithPassword:
+ setAuthMode('login');
+ setCurrentStep('enterPassword');
+
+ break;
}
};
- const handleOTPVerfied = (otpCode: string) => {
- if (authMode === 'register' && authType === 'email') {
- setCurrentStep('addPhoneNumber');
- }
+ const handleOTPVerfied = (registeredWithoutPhoneNumber: boolean = false) => {
+ // if (registeredWithoutPhoneNumber) {
+ // setCurrentStep('addPhoneNumber');
+ // }
};
const handleEditValue = () => {
- setCurrentStep('emailOrPassword');
+ setCurrentStep('emailOrPhone');
};
const handleCompleteSignUp = (countryCode: string, value: string) => {
setCurrentStep('addedPhoneNumberVerify');
};
- const handleCompleteSignUpOTPVerified = (otpCode: string) => {
- console.log(otpCode);
+ const handleCompleteSignUpOTPVerified = () => {
+ console.log('phoneNumberVerified');
};
const handleCompleteSignUpEditValue = () => {
- setCurrentStep('emailOrPassword');
+ setCurrentStep('emailOrPhone');
};
const handleLoggedInWithPassowrd = () => {};
return (
<>
- {currentStep === 'emailOrPassword' && (
+ {currentStep === 'emailOrPhone' && (
{
{currentStep === 'verify' && (
{
)}
{currentStep === 'addedPhoneNumberVerify' && (
-
)}
>
diff --git a/src/features/authorization/components/AuthenticationSteps/LoginRegiserForm.tsx b/src/features/authorization/components/AuthenticationSteps/LoginRegiserForm.tsx
index 66e7cbd..3d64449 100644
--- a/src/features/authorization/components/AuthenticationSteps/LoginRegiserForm.tsx
+++ b/src/features/authorization/components/AuthenticationSteps/LoginRegiserForm.tsx
@@ -15,29 +15,38 @@ import { isEmail } from '@/utils/regexes/isEmail';
import parsePhoneNumberFromString from 'libphonenumber-js';
import { AuthenticationCard } from '../AuthenticationCard';
import { CountryCodeSelector } from '../CountryCodeSelector';
+import type { UserStatus } from '../../types/userTypes';
+import { getUserStatusByPhoneNumberOrEmail } from '../../api/authorizationAPI';
+import { Toast } from '@/components/Toast';
+import type { CountryCode } from '@/types/commonTypes';
export interface LoginRegisterFormProps {
loginRegisterValue: string;
setLoginRegisterValue: Dispatch;
+ countryCode: CountryCode;
+ setCountryCode: Dispatch;
authType: AuthType;
setAuthType: Dispatch;
- onLoginRegisterSubmit: (value: string) => void;
+ onLoginRegisterSubmit: (value: string, userStatus: UserStatus) => void;
}
export function LoginRegisterForm({
loginRegisterValue,
setLoginRegisterValue,
+ countryCode,
+ setCountryCode,
authType,
setAuthType,
onLoginRegisterSubmit,
}: LoginRegisterFormProps) {
+ const [checkStatusLoading, setCheckStatusLoading] = useState(false);
const { t, i18n } = useTranslation('authentication');
- const [countryCode, setCountryCode] = useState('+98');
const textFieldRef = useRef(null);
const inputRef = useRef(null);
const dir = i18n.dir();
const [error, setError] = useState();
const [touched, setTouched] = useState(false);
+ const [errorMessage, setErrorMessage] = useState();
const inputError: boolean = touched && !!error;
const handleInputChange = (event: React.ChangeEvent) => {
@@ -91,9 +100,22 @@ export function LoginRegisterForm({
return true;
};
- const handleSubmit = () => {
+ const handleSubmit = async () => {
if (isInputValid(loginRegisterValue, authType)) {
- onLoginRegisterSubmit(loginRegisterValue);
+ setCheckStatusLoading(true);
+ const result = await getUserStatusByPhoneNumberOrEmail({
+ phoneNumber:
+ authType === 'phone' ? countryCode + loginRegisterValue : undefined,
+ email: authType === 'email' ? loginRegisterValue : undefined,
+ });
+ const jsonResult = await result.json();
+
+ if (jsonResult.success) {
+ onLoginRegisterSubmit(loginRegisterValue, jsonResult.userStatus);
+ } else {
+ setErrorMessage(jsonResult.message);
+ }
+ setCheckStatusLoading(false);
} else {
inputRef.current?.focus();
validateInput(loginRegisterValue, authType);
@@ -104,6 +126,14 @@ export function LoginRegisterForm({
return (
+ setErrorMessage(undefined)}
+ open={!!errorMessage}
+ >
+ {errorMessage}
+
+
{t('loginForm.title')}
@@ -139,8 +169,14 @@ export function LoginRegisterForm({
/>
-
- }>
+
+ }
+ >
{t('loginForm.loginWithGoogle')}
diff --git a/src/features/authorization/components/AuthenticationSteps/OtpVerifyForm.tsx b/src/features/authorization/components/AuthenticationSteps/OtpVerifyForm.tsx
index 54de8ba..748f7e9 100644
--- a/src/features/authorization/components/AuthenticationSteps/OtpVerifyForm.tsx
+++ b/src/features/authorization/components/AuthenticationSteps/OtpVerifyForm.tsx
@@ -6,25 +6,37 @@ import type { AuthMode, AuthType } from '../../types/authTypes';
import { useEffect, useState } from 'react';
import { Toast } from '@/components/Toast';
import { AuthenticationCard } from '../AuthenticationCard';
+import type { LoginRequest } from '../../types/userTypes';
+import { useSearchParams } from 'react-router';
+import {
+ loginOrSignUpWithOtp,
+ sendEmailOtp,
+ sendSmsOtp,
+} from '../../api/authorizationAPI';
+import type { CountryCode } from '@/types/commonTypes';
interface OtpVerifyFormProps {
value: string;
+ countryCode: CountryCode;
authType: AuthType;
authMode: AuthMode;
onEditValue: () => void;
- onOTPVerified: (otpCode: string) => void;
+ onOTPVerified: (registeredWithoutPhoneNumber: boolean) => void;
}
export function OtpVerifyForm({
value,
+ countryCode,
authType,
authMode,
onEditValue,
onOTPVerified,
}: OtpVerifyFormProps) {
+ const [searchParams] = useSearchParams();
const [otpCode, setOtpCode] = useState('');
const [otpDigitInvalid, setOtpDigitInvalid] = useState(false);
const [verifyStatus, setVerifyStatus] = useState<'success' | 'failed'>();
+ const [errorMessage, setErrorMessage] = useState();
const [verifyStatusLoading, setVerifyStatusLoading] =
useState(false);
const [verifyAlertOpen, setVerifyAlertOpen] = useState(false);
@@ -46,18 +58,18 @@ export function OtpVerifyForm({
return () => clearInterval(interval);
}, [resendTimer]);
- const handleResendOTPCode = () => {
+ const handleResendOTPCode = async () => {
setResendLoading(true);
- // TODO: Call API here instead of settimeout
+ if (authType === 'phone') {
+ await sendSmsOtp({ phoneNumber: countryCode + value });
+ } else {
+ await sendEmailOtp({ email: value });
+ }
- setTimeout(() => {
- console.log('resended');
-
- setResendTimer(120);
- setCanResend(false);
- setResendLoading(false);
- }, 1000);
+ setResendTimer(120);
+ setCanResend(false);
+ setResendLoading(false);
};
const formatTime = (seconds: number) => {
@@ -72,7 +84,7 @@ export function OtpVerifyForm({
setOtpCode(formattedValue);
};
- const handleVerifyOTP = () => {
+ const handleVerifyOTP = async () => {
if (!otpCode || otpCode.length < 4) {
setOtpDigitInvalid(true);
} else {
@@ -80,12 +92,26 @@ export function OtpVerifyForm({
setVerifyStatusLoading(true);
// Change setTimeout to api call
- setTimeout(() => {
- setVerifyAlertOpen(true);
+
+ const loginRequest: LoginRequest = {
+ otpCode: otpCode,
+ phoneNumber: authType === 'phone' ? countryCode + value : undefined,
+ email: authType === 'email' ? value : undefined,
+ returnUrl: searchParams.get('returnUrl') ?? '/',
+ };
+ const result = await loginOrSignUpWithOtp(loginRequest);
+ const jsonRes = await result.json();
+
+ if (jsonRes.success) {
setVerifyStatus('success');
- onOTPVerified(otpCode);
- setVerifyStatusLoading(false);
- }, 1000);
+ onOTPVerified(jsonRes.registeredWithOutPhoneNumber);
+ } else {
+ setVerifyStatus('failed');
+ setErrorMessage(jsonRes.message);
+ }
+
+ setVerifyAlertOpen(true);
+ setVerifyStatusLoading(false);
}
};
@@ -113,7 +139,7 @@ export function OtpVerifyForm({
const verifyAlertMessage = (): string => {
if (verifyStatus === 'failed') {
- return t('verify.theVerificationCodeIsIncorrect');
+ return errorMessage ?? t('verify.theVerificationCodeIsIncorrect');
} else if (verifyStatus === 'success' && authMode === 'register') {
return t('verify.youHaveSuccessfullySignedIn');
} else if (verifyStatus === 'success' && authMode === 'login') {
@@ -153,7 +179,7 @@ export function OtpVerifyForm({
endIcon={}
onClick={onEditValue}
>
- {value}
+ {authType === 'phone' ? countryCode + value : value}
diff --git a/src/features/authorization/components/AuthenticationSteps/VerifyPhoneNumber.tsx b/src/features/authorization/components/AuthenticationSteps/VerifyPhoneNumber.tsx
new file mode 100644
index 0000000..b047bdc
--- /dev/null
+++ b/src/features/authorization/components/AuthenticationSteps/VerifyPhoneNumber.tsx
@@ -0,0 +1,191 @@
+import { useTranslation } from 'react-i18next';
+import { Alert, Box, Button, Snackbar, Stack, Typography } from '@mui/material';
+import { Edit2 } from 'iconsax-reactjs';
+import DigitInput from '@/components/components/DigitsInput';
+import type { AuthMode, AuthType } from '../../types/authTypes';
+import { useEffect, useState } from 'react';
+import { Toast } from '@/components/Toast';
+import { AuthenticationCard } from '../AuthenticationCard';
+import type { LoginRequest } from '../../types/userTypes';
+import { useSearchParams } from 'react-router';
+import {
+ loginOrSignUpWithOtp,
+ sendEmailOtp,
+ sendSmsOtp,
+} from '../../api/authorizationAPI';
+import type { CountryCode } from '@/types/commonTypes';
+
+interface VerifyPhoneNumberProps {
+ value: string;
+ countryCode: CountryCode;
+ onEditValue: () => void;
+ onPhoneNumberVerified: () => void;
+}
+
+export function VerifyPhoneNumber({
+ value,
+ countryCode,
+ onEditValue,
+ onPhoneNumberVerified,
+}: VerifyPhoneNumberProps) {
+ const [searchParams] = useSearchParams();
+ const [otpCode, setOtpCode] = useState('');
+ const [otpDigitInvalid, setOtpDigitInvalid] = useState(false);
+ const [verifyStatus, setVerifyStatus] = useState<'success' | 'failed'>();
+ const [errorMessage, setErrorMessage] = useState();
+ const [verifyStatusLoading, setVerifyStatusLoading] =
+ useState(false);
+ const [verifyAlertOpen, setVerifyAlertOpen] = useState(false);
+ const { t } = useTranslation('authentication');
+ const [resendTimer, setResendTimer] = useState(120);
+ const [canResend, setCanResend] = useState(false);
+ const [resendLoading, setResendLoading] = useState(false);
+
+ useEffect(() => {
+ let interval: NodeJS.Timeout;
+ if (resendTimer > 0) {
+ interval = setInterval(() => {
+ setResendTimer((prev) => prev - 1);
+ }, 1000);
+ } else {
+ setCanResend(true);
+ }
+
+ return () => clearInterval(interval);
+ }, [resendTimer]);
+
+ const handleResendOTPCode = async () => {
+ setResendLoading(true);
+
+ await sendSmsOtp({ phoneNumber: countryCode + value });
+
+ setResendTimer(120);
+ setCanResend(false);
+ setResendLoading(false);
+ };
+
+ const formatTime = (seconds: number) => {
+ const min = Math.floor(seconds / 60);
+ const sec = seconds % 60;
+ return `${min}:${sec.toString().padStart(2, '0')}`;
+ };
+
+ const handleDigitInputChange = (value: string[]) => {
+ const formattedValue = value.filter((char) => char !== '').join('');
+
+ setOtpCode(formattedValue);
+ };
+
+ const handleVerifyOTP = async () => {
+ if (!otpCode || otpCode.length < 4) {
+ setOtpDigitInvalid(true);
+ } else {
+ setOtpDigitInvalid(false);
+ setVerifyStatusLoading(true);
+
+ // Change setTimeout to api call
+
+ // const loginRequest: LoginRequest = {
+ // otpCode: otpCode,
+ // phoneNumber: authType === 'phone' ? countryCode + value : undefined,
+ // email: authType === 'email' ? value : undefined,
+ // returnUrl: searchParams.get('returnUrl') ?? '/',
+ // };
+ // const result = await loginOrSignUpWithOtp(loginRequest);
+ // const jsonRes = await result.json();
+
+ // if (jsonRes.success) {
+ // setVerifyStatus('success');
+ // onOTPVerified(jsonRes.registeredWithOutPhoneNumber);
+ // } else {
+ // setVerifyStatus('failed');
+ // setErrorMessage(jsonRes.message);
+ // }
+
+ setVerifyAlertOpen(true);
+ setVerifyStatusLoading(false);
+ }
+ };
+
+ const verifyAlertMessage = (): string => {
+ if (verifyStatus === 'failed') {
+ return errorMessage ?? t('verify.theVerificationCodeIsIncorrect');
+ } else {
+ return t('verify.youHaveSuccessfullyLoggedIn');
+ }
+ };
+
+ return (
+
+
+ setVerifyAlertOpen(false)}
+ color={verifyStatus === 'failed' ? 'error' : 'success'}
+ >
+ {verifyAlertMessage()}
+
+
+
+ {t('verify.verify')}
+
+ }
+ onClick={onEditValue}
+ >
+ {countryCode + value}
+
+
+
+
+ {t(
+ 'verify.a4DigitVerificationCodeHasBeenSentToYourBobileNumberPleaseEnterIt',
+ )}
+
+
+ handleDigitInputChange(value as string[])}
+ />
+
+
+
+
+
+ {t('verify.resendCodeIn')}
+
+
+
+
+ );
+}
diff --git a/src/features/authorization/components/CountryCodeSelector.tsx b/src/features/authorization/components/CountryCodeSelector.tsx
index 427bc8a..04ad265 100644
--- a/src/features/authorization/components/CountryCodeSelector.tsx
+++ b/src/features/authorization/components/CountryCodeSelector.tsx
@@ -15,10 +15,11 @@ import ReactCountryFlag from 'react-country-flag';
import { useTranslation } from 'react-i18next';
import { Virtuoso } from 'react-virtuoso';
import { countries, type Country } from '../data/countries';
+import type { CountryCode } from '@/types/commonTypes';
interface CountryCodeSelectorProps {
show: boolean;
- value: string;
- onChange: (newValue: string) => void;
+ value: CountryCode;
+ onChange: (newValue: CountryCode) => void;
menuAnchor: HTMLElement | null;
onCloseFocusRef: RefObject;
}
diff --git a/src/features/authorization/data/countries.ts b/src/features/authorization/data/countries.ts
index 2b05f9e..a2a0166 100644
--- a/src/features/authorization/data/countries.ts
+++ b/src/features/authorization/data/countries.ts
@@ -1,13 +1,9 @@
-export interface Country {
- code: string;
- label: string;
- phone: string;
-}
+import type { CountryCode } from '@/types/commonTypes';
export interface Country {
code: string;
label: string;
- phone: string;
+ phone: CountryCode;
}
export const countries: readonly Country[] = [
diff --git a/src/features/authorization/types/userTypes.ts b/src/features/authorization/types/userTypes.ts
index 54070a0..74b5e1d 100644
--- a/src/features/authorization/types/userTypes.ts
+++ b/src/features/authorization/types/userTypes.ts
@@ -14,9 +14,9 @@ export interface GetUserStatusByPhoneNumberOrEmailResponse extends ApiResponse {
export enum UserStatus {
None = 0,
- Value1 = 1,
- Value2 = 2,
- Value3 = 3,
+ RegisteredWithPassword = 1,
+ RegisteredWithoutPassword = 2,
+ NotRegistered = 3,
}
// LoginOrSignUpWithOtp
@@ -104,3 +104,24 @@ export interface LoginOrSignUpWithGoogleResponse extends ApiResponse {
completedUserInformation: boolean;
returnUrl: string;
}
+
+// CompleteUserInformation
+
+export interface CompleteUserInformationRequest {
+ firstName?: string;
+ lastName?: string;
+ gender?: Gender;
+ nationalCode?: string;
+ savePassword?: boolean;
+ password?: string;
+ saveEmail?: boolean;
+ email?: string;
+ birthDate?: string;
+ countryCode?: string;
+ userId?: GUID;
+}
+
+export enum Gender {
+ Male = 1,
+ Female = 2,
+}
diff --git a/src/types/commonTypes.ts b/src/types/commonTypes.ts
index a380ad0..b6dee6b 100644
--- a/src/types/commonTypes.ts
+++ b/src/types/commonTypes.ts
@@ -1 +1,3 @@
export type GUID = `${string}-${string}-${string}-${string}-${string}`;
+
+export type CountryCode = `+${number}`;