diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 7c90784..cc58a0d 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -4,18 +4,18 @@
# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript
trigger:
-- develop
+ - develop
pool:
vmImage: ubuntu-latest
steps:
-- task: NodeTool@0
- inputs:
- versionSpec: '20.x'
- displayName: 'Install Node.js'
+ - task: NodeTool@0
+ inputs:
+ versionSpec: '20.x'
+ displayName: 'Install Node.js'
-- script: |
- npm install
- npm run build
- displayName: 'npm install and build'
+ - script: |
+ npm install
+ npm run build
+ displayName: 'npm install and build'
diff --git a/package-lock.json b/package-lock.json
index 1204723..bf02d1b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,6 +15,7 @@
"i18next": "^25.3.0",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
+ "iconsax-reactjs": "^0.0.8",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-i18next": "^15.6.0",
@@ -3102,6 +3103,15 @@
"cross-fetch": "4.0.0"
}
},
+ "node_modules/iconsax-reactjs": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/iconsax-reactjs/-/iconsax-reactjs-0.0.8.tgz",
+ "integrity": "sha512-cb+uTMxbkSFNbu8ZclX7BWQVfOWQt8+m/PsDjnsm/H+mcYrnfTYMjHxiof1FB43k7UAgt1ds+0oFeMVKdqyslw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
diff --git a/package.json b/package.json
index 666890c..563f7de 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"i18next": "^25.3.0",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
+ "iconsax-reactjs": "^0.0.8",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-i18next": "^15.6.0",
diff --git a/src/features/authentication/components/CountryCodeAdornment.tsx b/src/features/authentication/components/CountryCodeAdornment.tsx
deleted file mode 100644
index a7593d1..0000000
--- a/src/features/authentication/components/CountryCodeAdornment.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { Box, Typography } from '@mui/material';
-
-interface CountryCodeAdornmentProps {
- show: boolean;
-}
-
-/**
- * An animated country code adornment that fades and slides into view.
- * Its visibility is controlled by the `show` prop.
- */
-export function CountryCodeAdornment({ show }: CountryCodeAdornmentProps) {
- return (
-
- theme.transitions.create(['width', 'opacity'], {
- duration: theme.transitions.duration.short,
- }),
- // Prevent content from wrapping or spilling out during animation
- overflow: 'hidden',
- whiteSpace: 'nowrap',
- }}
- >
- {/* This inner Box prevents the content from being squeezed during the transition */}
-
-
- +41
-
-
-
- );
-}
diff --git a/src/features/authentication/components/CountryCodeSelector.tsx b/src/features/authentication/components/CountryCodeSelector.tsx
new file mode 100644
index 0000000..39a9c16
--- /dev/null
+++ b/src/features/authentication/components/CountryCodeSelector.tsx
@@ -0,0 +1,163 @@
+import {
+ Box,
+ ListItemIcon,
+ ListItemText,
+ Menu,
+ MenuItem,
+ TextField,
+ Typography,
+} from '@mui/material';
+import { useEffect, useMemo, useRef, useState, type RefObject } from 'react';
+import { countries, type Country } from '../data/countries';
+import { ArrowDown2 } from 'iconsax-reactjs';
+
+interface CountryCodeSelectorProps {
+ show: boolean;
+ value: string;
+ onChange: (newValue: string) => void;
+ menuAnchor: HTMLElement | null;
+ onCloseFocusRef: RefObject;
+}
+
+/**
+ * An animated country code adornment that fades and slides into view.
+ * Its visibility is controlled by the `show` prop.
+ */
+export function CountryCodeSelector({
+ show,
+ value,
+ onChange,
+ menuAnchor,
+ onCloseFocusRef,
+}: CountryCodeSelectorProps) {
+ const [anchorEl, setAnchorEl] = useState(null);
+ const [searchTerm, setSearchTerm] = useState('');
+ const searchInputRef = useRef(null);
+ const open = Boolean(anchorEl);
+ const menuWidth = menuAnchor ? menuAnchor.clientWidth : 'auto';
+
+ const handleClick = () => {
+ setAnchorEl(menuAnchor);
+ };
+
+ const handleClose = () => {
+ setTimeout(() => {
+ setAnchorEl(null);
+ }, 0);
+ setTimeout(() => {
+ onCloseFocusRef.current?.focus();
+ }, 100);
+ setSearchTerm(''); // Reset search on close
+ };
+
+ const handleSelect = (country: Country) => {
+ onChange(country.phone);
+ handleClose();
+ };
+
+ const handleMenuEntered = () => {
+ // Focus the input field after the menu has finished opening
+ searchInputRef.current?.focus();
+ };
+
+ useEffect(() => {
+ // console.log(open);
+ }, [open]);
+
+ const filteredCountries = useMemo(
+ () =>
+ countries.filter(
+ (country) =>
+ country.label.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ country.phone.includes(searchTerm),
+ ),
+ [searchTerm],
+ );
+
+ return (
+
+ theme.transitions.create(['width', 'opacity'], {
+ duration: theme.transitions.duration.standard,
+ }),
+ // Prevent content from wrapping or spilling out during animation
+ overflow: 'hidden',
+ whiteSpace: 'nowrap',
+
+ // layout styles
+ display: 'flex',
+ alignItems: 'center',
+ gap: 0.25,
+ pl: show ? 0.25 : 0,
+
+ '&:hover': {
+ cursor: 'pointer',
+ },
+ }}
+ >
+ {/* This inner Box prevents the content from being squeezed during the transition */}
+
+
+
+ {value}
+
+
+
+
+ );
+}
diff --git a/src/features/authentication/components/LoginForm.tsx b/src/features/authentication/components/LoginForm.tsx
index dedb027..0fe6d22 100644
--- a/src/features/authentication/components/LoginForm.tsx
+++ b/src/features/authentication/components/LoginForm.tsx
@@ -6,16 +6,20 @@ import {
TextField,
Typography,
} from '@mui/material';
-import { useState } from 'react';
+import { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { CountryCodeAdornment } from './CountryCodeAdornment';
+import { CountryCodeSelector } from './CountryCodeSelector';
+import { Google } from 'iconsax-reactjs';
const isNumeric = (value: string) => /^\d*$/.test(value);
export function LoginForm() {
const { t } = useTranslation('authentication');
const [value, setValue] = useState('');
+ const [countryCode, setCountryCode] = useState('+41');
const [inputType, setInputType] = useState<'phone' | 'email'>('phone');
+ const textFieldRef = useRef(null);
+ const inputRef = useRef(null);
const handleInputChange = (event: React.ChangeEvent) => {
const newValue = event.target.value;
@@ -41,21 +45,29 @@ export function LoginForm() {
theme.transitions.create('margin'),
}}
>
-
+
),
},
@@ -64,8 +76,12 @@ export function LoginForm() {
/>
-
-
+
+ }>
+ {t('loginForm.loginWithGoogle')}
+
);
diff --git a/src/features/authentication/data/countries.ts b/src/features/authentication/data/countries.ts
new file mode 100644
index 0000000..18bb14b
--- /dev/null
+++ b/src/features/authentication/data/countries.ts
@@ -0,0 +1,15 @@
+export interface Country {
+ code: string;
+ label: string;
+ phone: string;
+ flag: string;
+}
+
+export const countries: readonly Country[] = [
+ { code: 'CH', label: 'Switzerland', phone: '+41', flag: '🇨🇭' },
+ { code: 'SA', label: 'Saudi Arabia', phone: '+966', flag: '🇸🇦' },
+ { code: 'QA', label: 'Qatar', phone: '+974', flag: '🇶🇦' },
+ { code: 'KW', label: 'Kuwait', phone: '+965', flag: '🇰🇼' },
+ { code: 'BH', label: 'Bahrain', phone: '+973', flag: '🇧🇭' },
+ { code: 'AE', label: 'United Arab Emirates', phone: '+971', flag: '🇦🇪' },
+];