fix: base component dirs, remove duplicate component instance
This commit is contained in:
@@ -4,7 +4,7 @@ import { LanguageManager } from '@/components/LanguageManager';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
import { router } from '@/routes';
|
||||
import { useAuth } from './hooks/useAuth';
|
||||
import { Loading } from './components/Loading';
|
||||
import { Loading } from './components/routes/Loading';
|
||||
|
||||
function App() {
|
||||
const { authFinished } = useAuth();
|
||||
|
||||
16
src/assets/logo_icon.svg
Normal file
16
src/assets/logo_icon.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg width="30" height="24" viewBox="0 0 30 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M29.9995 12C29.9995 8.81744 28.7352 5.76516 26.4847 3.51472C24.2342 1.26429 21.1819 4.17348e-07 17.9993 0C14.8166 -4.17347e-07 11.7643 1.26429 9.51383 3.51472C7.26333 5.76516 5.99902 8.81744 5.99902 12H9.74915C10.9918 12 11.961 10.9587 12.4161 9.80234C12.715 9.04317 13.1684 8.3455 13.7565 7.75736C14.8818 6.63215 16.4079 6.00001 17.9993 6.00001C19.5906 6.00001 21.1168 6.63215 22.242 7.75736C23.3673 8.88258 23.9994 10.4087 23.9994 12H29.9995Z" fill="url(#paint0_linear_5679_13944)"/>
|
||||
<path d="M23.087 16.5922C23.6901 15.1363 24.0005 13.5758 24.0005 12H30C30 18.6274 24.6273 24 17.9997 24C16.9618 24 15.9546 23.8682 14.9941 23.6205C15.5376 23.4805 16.0717 23.3022 16.5925 23.0865C18.0485 22.4835 19.3713 21.5996 20.4857 20.4853C21.6 19.371 22.484 18.0481 23.087 16.5922Z" fill="#FBCF8E"/>
|
||||
<path d="M0 12C-2.25446e-07 13.5759 0.310395 15.1363 0.913462 16.5923C1.51653 18.0482 2.40046 19.371 3.51479 20.4854C4.62911 21.5996 5.95201 22.4835 7.40795 23.0866C8.86387 23.6897 10.4243 24 12.0003 24C13.5761 24 15.1366 23.6897 16.5925 23.0866C18.0485 22.4835 19.3714 21.5996 20.4857 20.4854C21.6 19.371 22.4839 18.0482 23.087 16.5923C23.6901 15.1363 24.0005 13.5759 24.0005 12H20.2504C19.0077 12 18.0386 13.0414 17.5834 14.1976C17.5705 14.2306 17.5572 14.2634 17.5436 14.2962C17.2421 15.0241 16.8001 15.6856 16.2429 16.2427C15.6858 16.7998 15.0243 17.2418 14.2964 17.5433C13.5685 17.8449 12.7882 18.0001 12.0003 18.0001C11.2123 18.0001 10.4321 17.8449 9.70413 17.5433C8.97612 17.2418 8.31471 16.7998 7.7575 16.2427C7.20035 15.6856 6.75839 15.0241 6.45686 14.2962C6.15532 13.5682 6.00012 12.788 6.00012 12H0Z" fill="url(#paint1_linear_5679_13944)"/>
|
||||
<path d="M0.000976562 12C0.000976562 5.37257 5.37365 0 12.0012 0C13.0388 0 14.0457 0.131696 15.0061 0.379295C12.9454 0.91015 11.0444 1.98446 9.51404 3.51473C7.26357 5.76515 5.99928 8.81744 5.99928 12H0.000976562Z" fill="#73E5E2"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_5679_13944" x1="29.9995" y1="6.00001" x2="5.99902" y2="6.00001" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#73E5E2"/>
|
||||
<stop offset="1" stop-color="#394F87"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_5679_13944" x1="7.8682e-07" y1="18.0001" x2="24.0005" y2="18.0001" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FBDC8E"/>
|
||||
<stop offset="1" stop-color="#FB958E"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -12,7 +12,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';
|
||||
import type { CountryCode } from '@/types/commonTypes';
|
||||
import { Icon } from '@rkheftan/harmony-ui';
|
||||
import { LTRTypography } from '@/components/common/LTRTypography';
|
||||
@@ -40,7 +40,7 @@ export function CountryCodeSelector({
|
||||
const open = Boolean(anchorEl);
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
const menuWidth = menuAnchor ? menuAnchor.clientWidth : 'auto';
|
||||
const { t } = useTranslation(['country', 'common']);
|
||||
const { t, i18n } = useTranslation(['country', 'common']);
|
||||
|
||||
const selectedCountry =
|
||||
countries.find((c) => c.phone === value) || countries[0];
|
||||
@@ -106,7 +106,7 @@ export function CountryCodeSelector({
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row-reverse',
|
||||
flexDirection: i18n.dir() === 'ltr' ? 'row-reverse' : 'row',
|
||||
gap: 0.25,
|
||||
pl: show ? 0.25 : 0,
|
||||
|
||||
@@ -147,11 +147,11 @@ export function CountryCodeSelector({
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
slotProps={{
|
||||
list: {
|
||||
paper: {
|
||||
sx: {
|
||||
width: menuWidth,
|
||||
height: '18.75rem',
|
||||
p: 0,
|
||||
maxHeight: '20rem',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
},
|
||||
transition: { onEntered: handleMenuEntered },
|
||||
@@ -160,6 +160,7 @@ export function CountryCodeSelector({
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
|
||||
>
|
||||
<Box
|
||||
tabIndex={-1}
|
||||
sx={{
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
@@ -178,7 +179,7 @@ export function CountryCodeSelector({
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ width: '100%', height: '14.75rem' }}>
|
||||
<Box component="div" sx={{ width: '100%', height: '25rem' }}>
|
||||
{filteredCountries.length === 0 ? (
|
||||
<MenuItem disabled>
|
||||
<ListItem>
|
||||
@@ -1,7 +1,25 @@
|
||||
import LogoSvg from '@/assets/logo.svg';
|
||||
import LogoIconSvg from '@/assets/logo_icon.svg';
|
||||
import { Box, type SxProps } from '@mui/material';
|
||||
|
||||
function Logo() {
|
||||
return <img src={LogoSvg} style={{ width: '150px', height: 'auto' }} />;
|
||||
interface LogoProps {
|
||||
boxSx?: SxProps;
|
||||
isIcon?: boolean;
|
||||
}
|
||||
|
||||
function Logo({ boxSx, isIcon = false }: LogoProps) {
|
||||
// TODO: handle transition
|
||||
return (
|
||||
<Box
|
||||
component="img"
|
||||
src={isIcon ? LogoIconSvg : LogoSvg}
|
||||
sx={{
|
||||
width: isIcon ? '30px' : '120px',
|
||||
height: 'auto',
|
||||
...boxSx,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Logo;
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Alert, Snackbar, type AlertColor } from '@mui/material';
|
||||
import { type PropsWithChildren } from 'react';
|
||||
|
||||
export interface ToastProps extends PropsWithChildren {
|
||||
color: AlertColor | undefined;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const Toast = ({ color, open, onClose, children }: ToastProps) => {
|
||||
return (
|
||||
<Snackbar
|
||||
sx={{ width: (t) => `calc(100% - ${t.spacing(6)})`, maxWidth: '396px' }}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
>
|
||||
<Alert
|
||||
onClose={onClose}
|
||||
severity={color}
|
||||
variant="filled"
|
||||
sx={{ width: '100%' }}
|
||||
>
|
||||
{children}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
);
|
||||
};
|
||||
@@ -85,9 +85,7 @@ export default function ProductsMenu({
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
<Box sx={{ width: 69 }}>
|
||||
<product.LogoComponent />
|
||||
</Box>
|
||||
<Box sx={{ width: 69 }}>{product.LogoComponent}</Box>
|
||||
</Stack>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import Logo from '@/components/Logo';
|
||||
import { type FC } from 'react';
|
||||
import type { ReactElement } from 'react';
|
||||
|
||||
export interface Product {
|
||||
id: string;
|
||||
titleKey: string;
|
||||
descriptionKey: string;
|
||||
LogoComponent: FC;
|
||||
demoLink: string | null;
|
||||
LogoComponent: ReactElement;
|
||||
demoLink: string;
|
||||
}
|
||||
|
||||
export const productsData: Product[] = [
|
||||
@@ -14,8 +14,8 @@ export const productsData: Product[] = [
|
||||
id: 'harmony-club',
|
||||
titleKey: 'products.harmonyClub.title',
|
||||
descriptionKey: 'products.harmonyClub.description',
|
||||
LogoComponent: Logo, // Reference the component
|
||||
demoLink: null, // FIXME: update this url
|
||||
LogoComponent: <Logo isIcon boxSx={{ width: 69, height: 55 }} />, // Reference the component
|
||||
demoLink: '', // FIXME: update this url
|
||||
},
|
||||
// add more products here
|
||||
];
|
||||
@@ -3,7 +3,7 @@ import parsePhoneNumberFromString from 'libphonenumber-js';
|
||||
import { useRef, useState, type Dispatch } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AuthenticationCard } from '../AuthenticationCard';
|
||||
import { CountryCodeSelector } from '../CountryCodeSelector';
|
||||
import { CountryCodeSelector } from '../../../../components/CountryCodeSelector';
|
||||
import { sendSmsOtp } from '../../api/authorizationAPI';
|
||||
import type { CountryCode } from '@/types/commonTypes';
|
||||
import { useApi } from '@/hooks/useApi';
|
||||
|
||||
@@ -5,7 +5,7 @@ import { isNumeric } from '@/utils/regexes/isNumeric';
|
||||
import type { AuthFactory, AuthType } from '../../types/authTypes';
|
||||
import { isEmail } from '@/utils/regexes/isEmail';
|
||||
import { AuthenticationCard } from '../AuthenticationCard';
|
||||
import { CountryCodeSelector } from '../CountryCodeSelector';
|
||||
import { CountryCodeSelector } from '../../../../components/CountryCodeSelector';
|
||||
import type { LoginResult, UserStatus } from '../../types/userTypes';
|
||||
import { getUserStatusByPhoneNumberOrEmail } from '../../api/authorizationAPI';
|
||||
import type { CountryCode } from '@/types/commonTypes';
|
||||
|
||||
@@ -5,7 +5,7 @@ import { isNumeric } from '@/utils/regexes/isNumeric';
|
||||
import type { AuthType } from '../../types/authTypes';
|
||||
import { isEmail } from '@/utils/regexes/isEmail';
|
||||
import { AuthenticationCard } from '../AuthenticationCard';
|
||||
import { CountryCodeSelector } from '../CountryCodeSelector';
|
||||
import { CountryCodeSelector } from '../../../../components/CountryCodeSelector';
|
||||
import type { CountryCode } from '@/types/commonTypes';
|
||||
import { sendForgetPassCode } from '../../api/authorizationAPI';
|
||||
import type { SendForgetPassCodeRequest } from '../../types/userTypes';
|
||||
|
||||
@@ -1,213 +0,0 @@
|
||||
import {
|
||||
Box,
|
||||
InputAdornment,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Menu,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { useMemo, useRef, useState, type RefObject } from 'react';
|
||||
import { ArrowDown2 } from 'iconsax-react';
|
||||
import ReactCountryFlag from 'react-country-flag';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { countries, type Country } from '../../../data/countries';
|
||||
import type { CountryCode } from '@/types/commonTypes';
|
||||
import { Icon } from '@rkheftan/harmony-ui';
|
||||
import { LTRBox } from '@/components/common/LTRBox';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
|
||||
interface CountryCodeSelectorProps {
|
||||
show: boolean;
|
||||
value: CountryCode;
|
||||
onChange: (newValue: CountryCode) => void;
|
||||
menuAnchor: HTMLElement | null;
|
||||
onCloseFocusRef: RefObject<HTMLInputElement | null>;
|
||||
}
|
||||
|
||||
export function CountryCodeSelector({
|
||||
show,
|
||||
value,
|
||||
onChange,
|
||||
menuAnchor,
|
||||
onCloseFocusRef,
|
||||
}: CountryCodeSelectorProps) {
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const open = Boolean(anchorEl);
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
const menuWidth = menuAnchor ? menuAnchor.clientWidth : 'auto';
|
||||
const { t, i18n } = useTranslation(['country', 'common']);
|
||||
|
||||
const selectedCountry =
|
||||
countries.find((c) => c.phone === value) || countries[0];
|
||||
|
||||
const handleClick = () => setAnchorEl(menuAnchor);
|
||||
|
||||
const handleClose = () => {
|
||||
setTimeout(() => setAnchorEl(null), 0);
|
||||
setTimeout(() => {
|
||||
onCloseFocusRef.current?.focus();
|
||||
}, 100);
|
||||
setSearchTerm('');
|
||||
};
|
||||
|
||||
const handleSelect = (country: Country) => {
|
||||
onChange(country.phone);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handleMenuEntered = () => {
|
||||
searchInputRef.current?.focus();
|
||||
};
|
||||
|
||||
const filteredCountries = useMemo(
|
||||
() =>
|
||||
countries.filter(
|
||||
(country) =>
|
||||
t(country.label, { ns: 'country' })
|
||||
.toLowerCase()
|
||||
.includes(searchTerm.toLowerCase()) ||
|
||||
country.label.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
country.phone.includes(searchTerm),
|
||||
),
|
||||
[searchTerm, t],
|
||||
);
|
||||
|
||||
const initialIndex = useMemo(() => {
|
||||
const index = filteredCountries.findIndex((c) => c.phone === value);
|
||||
return Math.max(0, index);
|
||||
}, [filteredCountries, value]);
|
||||
|
||||
return (
|
||||
<InputAdornment
|
||||
position={i18n.dir() === 'rtl' ? 'start' : 'end'}
|
||||
sx={{ mx: 0 }}
|
||||
>
|
||||
<Box
|
||||
onClick={handleClick}
|
||||
sx={{
|
||||
width: show ? 'auto' : 0,
|
||||
opacity: show ? 1 : 0,
|
||||
transition: (theme) =>
|
||||
theme.transitions.create(['width', 'opacity'], {
|
||||
duration: theme.transitions.duration.standard,
|
||||
}),
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
height: '200%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 0.25,
|
||||
pl: show ? 0.25 : 0,
|
||||
'&:hover': { cursor: 'pointer' },
|
||||
}}
|
||||
>
|
||||
<Icon Component={ArrowDown2} size="medium" variant="Bold" />
|
||||
|
||||
<Typography
|
||||
variant="body1"
|
||||
color="text.primary"
|
||||
component="div"
|
||||
sx={{ direction: 'rtl' }}
|
||||
>
|
||||
{value}
|
||||
</Typography>
|
||||
|
||||
<ReactCountryFlag
|
||||
countryCode={selectedCountry.code}
|
||||
svg
|
||||
style={{
|
||||
height: '1.5rem',
|
||||
width: '1.5rem',
|
||||
marginTop: '-2px',
|
||||
marginRight: '4px',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
slotProps={{
|
||||
paper: {
|
||||
sx: {
|
||||
width: menuWidth,
|
||||
maxHeight: '20rem',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
},
|
||||
transition: { onEntered: handleMenuEntered },
|
||||
}}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 1,
|
||||
p: 1,
|
||||
backgroundColor: 'background.paper',
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
inputRef={searchInputRef}
|
||||
size="small"
|
||||
fullWidth
|
||||
label={t('labels.search', { ns: 'common' })}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ width: '100%', height: '25rem' }}>
|
||||
{filteredCountries.length === 0 ? (
|
||||
<MenuItem disabled>
|
||||
<ListItem>
|
||||
<ListItemText>
|
||||
{t('messages.noResualtFound', { ns: 'common' })}
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
</MenuItem>
|
||||
) : (
|
||||
<Virtuoso
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
data={filteredCountries}
|
||||
initialTopMostItemIndex={initialIndex}
|
||||
itemContent={(_, country) => (
|
||||
<MenuItem
|
||||
key={country.code}
|
||||
selected={country.phone === value}
|
||||
onClick={() => handleSelect(country)}
|
||||
sx={{ justifyContent: 'space-between' }}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<ListItemIcon sx={{ minWidth: 'auto' }}>
|
||||
<ReactCountryFlag
|
||||
countryCode={country.code}
|
||||
svg
|
||||
style={{ height: '1.125rem', width: '1.125rem' }}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t(country.label, { ns: 'country' })}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Typography color="text.secondary">
|
||||
<LTRBox>{country.phone}</LTRBox>
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
)}
|
||||
computeItemKey={(_, c) => c.code}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Menu>
|
||||
</Box>
|
||||
</InputAdornment>
|
||||
);
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import { Icon } from '@rkheftan/harmony-ui';
|
||||
import { type PhoneEditFormProps } from '@/features/profile/types/settingsType';
|
||||
import { useRef } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { CountryCodeSelector } from '@/features/authentication/components/CountryCodeSelector';
|
||||
import { CountryCodeSelector } from '@/components/CountryCodeSelector';
|
||||
import { LTRTypography } from '@/components/common/LTRTypography';
|
||||
|
||||
export default function PhoneEditForm({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Layout } from '@/components';
|
||||
import { NavigateWithToast } from '@/components/NavigateWithToast';
|
||||
import { NavigateWithToast } from '@/components/routes/NavigateWithToast';
|
||||
import { ProfileProvider } from '@/features/profile/providers/ProfileProvider';
|
||||
import {
|
||||
Calendar,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Suspense, type ReactNode } from 'react';
|
||||
import { createBrowserRouter, type RouteObject } from 'react-router-dom';
|
||||
import { appRoutes, type RouteConfig } from './config';
|
||||
import { ProtectedRoute } from '@/components/ProtectedRoute';
|
||||
import { Loading } from '@/components/Loading';
|
||||
import { ProtectedRoute } from '@/components/routes/ProtectedRoute';
|
||||
import { Loading } from '@/components/routes/Loading';
|
||||
|
||||
/**
|
||||
* A recursive function to map our custom route config to the format
|
||||
|
||||
Reference in New Issue
Block a user