mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-17 01:17:29 +02:00
feat: admin menu for mobile (#9626)
This commit is contained in:
parent
6b793677b9
commit
f7c04cc2cb
@ -72,6 +72,7 @@ interface IMenuGroupProps {
|
|||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
activeIcon: ReactNode;
|
activeIcon: ReactNode;
|
||||||
isActiveMenu: boolean;
|
isActiveMenu: boolean;
|
||||||
|
staticExpanded?: true | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MenuGroup = ({
|
export const MenuGroup = ({
|
||||||
@ -80,10 +81,12 @@ export const MenuGroup = ({
|
|||||||
icon,
|
icon,
|
||||||
activeIcon,
|
activeIcon,
|
||||||
isActiveMenu,
|
isActiveMenu,
|
||||||
|
staticExpanded,
|
||||||
}: IMenuGroupProps) => {
|
}: IMenuGroupProps) => {
|
||||||
return (
|
return (
|
||||||
<StyledAccordion
|
<StyledAccordion
|
||||||
disableGutters={true}
|
disableGutters={true}
|
||||||
|
expanded={staticExpanded}
|
||||||
sx={{
|
sx={{
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
'&:before': {
|
'&:before': {
|
||||||
@ -92,7 +95,7 @@ export const MenuGroup = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StyledAccordionSummary
|
<StyledAccordionSummary
|
||||||
expandIcon={<ExpandMoreIcon />}
|
expandIcon={staticExpanded ? null : <ExpandMoreIcon />}
|
||||||
aria-controls='configure-content'
|
aria-controls='configure-content'
|
||||||
id='configure-header'
|
id='configure-header'
|
||||||
sx={listItemButtonStyle}
|
sx={listItemButtonStyle}
|
||||||
|
@ -1,38 +1,8 @@
|
|||||||
import {
|
import { Grid, styled, Paper, useMediaQuery, useTheme } from '@mui/material';
|
||||||
Grid,
|
|
||||||
styled,
|
|
||||||
Paper,
|
|
||||||
Typography,
|
|
||||||
Button,
|
|
||||||
List,
|
|
||||||
useMediaQuery,
|
|
||||||
useTheme,
|
|
||||||
} from '@mui/material';
|
|
||||||
import { useUiFlag } from 'hooks/useUiFlag';
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
|
||||||
import StopRoundedIcon from '@mui/icons-material/StopRounded';
|
|
||||||
import { AdminListItem, AdminSubListItem, MenuGroup } from './AdminListItem';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
import { Sticky } from 'component/common/Sticky/Sticky';
|
import { Sticky } from 'component/common/Sticky/Sticky';
|
||||||
import { adminRoutes, adminGroups } from 'component/admin/adminRoutes';
|
import { AdminMenuNavigation } from './AdminNavigationItems';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
import { filterByConfig } from 'component/common/util';
|
|
||||||
import { filterAdminRoutes } from 'component/admin/filterAdminRoutes';
|
|
||||||
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
|
|
||||||
import { IconRenderer } from './AdminMenuIcons';
|
|
||||||
|
|
||||||
interface IMenuLinkItem {
|
|
||||||
href: string;
|
|
||||||
text: string;
|
|
||||||
icon: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IMenuItem {
|
|
||||||
href: string;
|
|
||||||
text: string;
|
|
||||||
items?: IMenuLinkItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const StyledAdminMainGrid = styled(Grid)(({ theme }) => ({
|
const StyledAdminMainGrid = styled(Grid)(({ theme }) => ({
|
||||||
minWidth: 0, // this is a fix for overflowing flex
|
minWidth: 0, // this is a fix for overflowing flex
|
||||||
@ -79,20 +49,6 @@ const StickyContainer = styled(Sticky)(({ theme }) => ({
|
|||||||
transition: 'padding 0.3s ease',
|
transition: 'padding 0.3s ease',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const SettingsHeader = styled(Typography)(({ theme }) => ({
|
|
||||||
fontSize: theme.fontSizes.mainHeader,
|
|
||||||
fontWeight: theme.fontWeight.bold,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledButton = styled(Button)(({ theme }) => ({
|
|
||||||
paddingLeft: theme.spacing(0),
|
|
||||||
marginBottom: theme.spacing(3),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledStopRoundedIcon = styled(StopRoundedIcon)(({ theme }) => ({
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
}));
|
|
||||||
|
|
||||||
interface IWrapIfAdminSubpageProps {
|
interface IWrapIfAdminSubpageProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
@ -113,29 +69,11 @@ export const WrapIfAdminSubpage = ({ children }: IWrapIfAdminSubpageProps) => {
|
|||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DashboardLink = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<StyledButton
|
|
||||||
href='/personal'
|
|
||||||
rel='noreferrer'
|
|
||||||
startIcon={<ArrowBackIcon />}
|
|
||||||
>
|
|
||||||
Back to Unleash
|
|
||||||
</StyledButton>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IAdminMenuProps {
|
interface IAdminMenuProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AdminMenu = ({ children }: IAdminMenuProps) => {
|
export const AdminMenu = ({ children }: IAdminMenuProps) => {
|
||||||
const { uiConfig, isPro, isEnterprise } = useUiConfig();
|
|
||||||
const { isBilling } = useInstanceStatus();
|
|
||||||
const isActiveItem = (item?: string) =>
|
|
||||||
item !== undefined && location.pathname === item;
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isBreakpoint = useMediaQuery(theme.breakpoints.down(1350));
|
const isBreakpoint = useMediaQuery(theme.breakpoints.down(1350));
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
@ -144,109 +82,13 @@ export const AdminMenu = ({ children }: IAdminMenuProps) => {
|
|||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const location = useLocation();
|
|
||||||
const routes = adminRoutes
|
|
||||||
.filter(filterByConfig(uiConfig))
|
|
||||||
.filter((route) =>
|
|
||||||
filterAdminRoutes(route?.menu, {
|
|
||||||
enterprise: isEnterprise(),
|
|
||||||
pro: isPro(),
|
|
||||||
billing: isBilling,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const menuStructure = routes.reduce(
|
|
||||||
(acc: Record<string, IMenuItem>, route) => {
|
|
||||||
if (route.group && adminGroups[route.group]) {
|
|
||||||
if (!acc[route.group]) {
|
|
||||||
acc[route.group] = {
|
|
||||||
href: route.group,
|
|
||||||
text: adminGroups[route.group],
|
|
||||||
items: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
acc[route.group].items?.push({
|
|
||||||
href: route.path,
|
|
||||||
text: route.title,
|
|
||||||
icon: <StopRoundedIcon />,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!route.group) {
|
|
||||||
acc[route.path] = {
|
|
||||||
href: route.path,
|
|
||||||
text: route.title,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
|
|
||||||
const items = Object.values(menuStructure);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledAdminMainGrid container spacing={1}>
|
<StyledAdminMainGrid container spacing={1}>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<StickyContainer>
|
<StickyContainer>
|
||||||
<StyledMenuPaper>
|
<StyledMenuPaper>
|
||||||
<SettingsHeader>Admin settings</SettingsHeader>
|
<AdminMenuNavigation onClick={onClick} />
|
||||||
<DashboardLink />
|
|
||||||
<List>
|
|
||||||
{items.map((item) => {
|
|
||||||
if (item.items) {
|
|
||||||
const isActiveMenu = item.items.find(
|
|
||||||
(itm) => isActiveItem(itm.href),
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<MenuGroup
|
|
||||||
title={item.text}
|
|
||||||
icon={
|
|
||||||
<IconRenderer
|
|
||||||
path={item.href}
|
|
||||||
active={false}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
activeIcon={
|
|
||||||
<IconRenderer
|
|
||||||
path={item.href}
|
|
||||||
active={true}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
isActiveMenu={Boolean(isActiveMenu)}
|
|
||||||
key={item.text}
|
|
||||||
>
|
|
||||||
{item.items.map((subItem) => (
|
|
||||||
<AdminSubListItem
|
|
||||||
href={subItem.href}
|
|
||||||
text={subItem.text}
|
|
||||||
selected={isActiveItem(
|
|
||||||
subItem.href,
|
|
||||||
)}
|
|
||||||
onClick={onClick}
|
|
||||||
key={subItem.href}
|
|
||||||
>
|
|
||||||
<StyledStopRoundedIcon />
|
|
||||||
</AdminSubListItem>
|
|
||||||
))}
|
|
||||||
</MenuGroup>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<AdminListItem
|
|
||||||
href={item.href}
|
|
||||||
text={item.text}
|
|
||||||
selected={isActiveItem(item.href)}
|
|
||||||
onClick={onClick}
|
|
||||||
key={item.href}
|
|
||||||
>
|
|
||||||
<IconRenderer
|
|
||||||
path={item.href}
|
|
||||||
active={false}
|
|
||||||
/>
|
|
||||||
</AdminListItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</List>
|
|
||||||
</StyledMenuPaper>
|
</StyledMenuPaper>
|
||||||
</StickyContainer>
|
</StickyContainer>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -0,0 +1,185 @@
|
|||||||
|
import { Button, styled, Typography, List } from '@mui/material';
|
||||||
|
import { OtherLinksList } from '../NavigationSidebar/NavigationList';
|
||||||
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||||
|
import StopRoundedIcon from '@mui/icons-material/StopRounded';
|
||||||
|
import { AdminListItem, AdminSubListItem, MenuGroup } from './AdminListItem';
|
||||||
|
import { IconRenderer } from './AdminMenuIcons';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { filterByConfig } from 'component/common/util';
|
||||||
|
import { filterAdminRoutes } from 'component/admin/filterAdminRoutes';
|
||||||
|
import { adminGroups, adminRoutes } from 'component/admin/adminRoutes';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface IMenuLinkItem {
|
||||||
|
href: string;
|
||||||
|
text: string;
|
||||||
|
icon: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IMenuItem {
|
||||||
|
href: string;
|
||||||
|
text: string;
|
||||||
|
items?: IMenuLinkItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const SettingsHeader = styled(Typography)(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.mainHeader,
|
||||||
|
fontWeight: theme.fontWeight.bold,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledButton = styled(Button)(({ theme }) => ({
|
||||||
|
paddingLeft: theme.spacing(0),
|
||||||
|
marginBottom: theme.spacing(3),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledDiv = styled('div')(({ theme }) => ({
|
||||||
|
padding: theme.spacing(2, 2.5, 0, 2.5),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledStopRoundedIcon = styled(StopRoundedIcon)(({ theme }) => ({
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const DashboardLink = () => {
|
||||||
|
return (
|
||||||
|
<StyledButton
|
||||||
|
href='/personal'
|
||||||
|
rel='noreferrer'
|
||||||
|
startIcon={<ArrowBackIcon />}
|
||||||
|
>
|
||||||
|
Back to Unleash
|
||||||
|
</StyledButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdminMobileNavigation = ({ onClick }: { onClick: () => void }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StyledDiv>
|
||||||
|
<AdminNavigationHeader />
|
||||||
|
</StyledDiv>
|
||||||
|
|
||||||
|
<AdminNavigationItems staticExpanded={true} onClick={onClick} />
|
||||||
|
|
||||||
|
<OtherLinksList />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdminMenuNavigation = ({ onClick }: { onClick: () => void }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AdminNavigationHeader />
|
||||||
|
<AdminNavigationItems onClick={onClick} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdminNavigationHeader = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SettingsHeader>Admin settings</SettingsHeader>
|
||||||
|
<DashboardLink />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdminNavigationItems = ({
|
||||||
|
onClick,
|
||||||
|
staticExpanded,
|
||||||
|
}: { onClick: () => void; staticExpanded?: true | undefined }) => {
|
||||||
|
const { uiConfig, isPro, isEnterprise } = useUiConfig();
|
||||||
|
const { isBilling } = useInstanceStatus();
|
||||||
|
const isActiveItem = (item?: string) =>
|
||||||
|
item !== undefined && location.pathname === item;
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const routes = adminRoutes
|
||||||
|
.filter(filterByConfig(uiConfig))
|
||||||
|
.filter((route) =>
|
||||||
|
filterAdminRoutes(route?.menu, {
|
||||||
|
enterprise: isEnterprise(),
|
||||||
|
pro: isPro(),
|
||||||
|
billing: isBilling,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuStructure = routes.reduce(
|
||||||
|
(acc: Record<string, IMenuItem>, route) => {
|
||||||
|
if (route.group && adminGroups[route.group]) {
|
||||||
|
if (!acc[route.group]) {
|
||||||
|
acc[route.group] = {
|
||||||
|
href: route.group,
|
||||||
|
text: adminGroups[route.group],
|
||||||
|
items: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
acc[route.group].items?.push({
|
||||||
|
href: route.path,
|
||||||
|
text: route.title,
|
||||||
|
icon: <StopRoundedIcon />,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!route.group) {
|
||||||
|
acc[route.path] = {
|
||||||
|
href: route.path,
|
||||||
|
text: route.title,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
const items = Object.values(menuStructure);
|
||||||
|
return (
|
||||||
|
<List>
|
||||||
|
{items.map((item) => {
|
||||||
|
if (item.items) {
|
||||||
|
const isActiveMenu = item.items.find((itm) =>
|
||||||
|
isActiveItem(itm.href),
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<MenuGroup
|
||||||
|
title={item.text}
|
||||||
|
icon={
|
||||||
|
<IconRenderer path={item.href} active={false} />
|
||||||
|
}
|
||||||
|
activeIcon={
|
||||||
|
<IconRenderer path={item.href} active={true} />
|
||||||
|
}
|
||||||
|
isActiveMenu={Boolean(isActiveMenu)}
|
||||||
|
key={item.text}
|
||||||
|
staticExpanded={staticExpanded}
|
||||||
|
>
|
||||||
|
{item.items.map((subItem) => (
|
||||||
|
<AdminSubListItem
|
||||||
|
href={subItem.href}
|
||||||
|
text={subItem.text}
|
||||||
|
selected={isActiveItem(subItem.href)}
|
||||||
|
onClick={onClick}
|
||||||
|
key={subItem.href}
|
||||||
|
>
|
||||||
|
<StyledStopRoundedIcon />
|
||||||
|
</AdminSubListItem>
|
||||||
|
))}
|
||||||
|
</MenuGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<AdminListItem
|
||||||
|
href={item.href}
|
||||||
|
text={item.text}
|
||||||
|
selected={isActiveItem(item.href)}
|
||||||
|
onClick={onClick}
|
||||||
|
key={item.href}
|
||||||
|
>
|
||||||
|
<IconRenderer path={item.href} active={false} />
|
||||||
|
</AdminListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import type { ReactNode, VFC } from 'react';
|
import type { VFC } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Divider, Drawer, styled } from '@mui/material';
|
import { Divider, Drawer, styled } from '@mui/material';
|
||||||
import { ReactComponent as UnleashLogo } from 'assets/img/logoDarkWithText.svg';
|
import { ReactComponent as UnleashLogo } from 'assets/img/logoDarkWithText.svg';
|
||||||
@ -8,6 +8,8 @@ import theme from 'themes/theme';
|
|||||||
import { ThemeMode } from 'component/common/ThemeMode/ThemeMode';
|
import { ThemeMode } from 'component/common/ThemeMode/ThemeMode';
|
||||||
import { MobileNavigationSidebar } from 'component/layout/MainLayout/NavigationSidebar/NavigationSidebar';
|
import { MobileNavigationSidebar } from 'component/layout/MainLayout/NavigationSidebar/NavigationSidebar';
|
||||||
import { NewInUnleash } from 'component/layout/MainLayout/NavigationSidebar/NewInUnleash/NewInUnleash';
|
import { NewInUnleash } from 'component/layout/MainLayout/NavigationSidebar/NewInUnleash/NewInUnleash';
|
||||||
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
|
import { AdminMobileNavigation } from 'component/layout/MainLayout/AdminMenu/AdminNavigationItems';
|
||||||
|
|
||||||
const StyledDrawerHeader = styled('div')(({ theme }) => ({
|
const StyledDrawerHeader = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -25,19 +27,18 @@ interface IDrawerMenuProps {
|
|||||||
title?: string;
|
title?: string;
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
toggleDrawer: () => void;
|
toggleDrawer: () => void;
|
||||||
links: Array<{
|
|
||||||
value: string;
|
|
||||||
icon: ReactNode;
|
|
||||||
href: string;
|
|
||||||
title: string;
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DrawerMenu: VFC<IDrawerMenuProps> = ({
|
export const DrawerMenu: VFC<IDrawerMenuProps> = ({
|
||||||
links = [],
|
|
||||||
open = false,
|
open = false,
|
||||||
toggleDrawer,
|
toggleDrawer,
|
||||||
}) => {
|
}) => {
|
||||||
|
const newAdminUIEnabled = useUiFlag('adminNavUI');
|
||||||
|
const showOnlyAdminMenu =
|
||||||
|
newAdminUIEnabled && location.pathname.indexOf('/admin') === 0;
|
||||||
|
const onClick = () => {
|
||||||
|
toggleDrawer();
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
className={styles.drawer}
|
className={styles.drawer}
|
||||||
@ -65,10 +66,14 @@ export const DrawerMenu: VFC<IDrawerMenuProps> = ({
|
|||||||
</Link>
|
</Link>
|
||||||
</StyledDrawerHeader>
|
</StyledDrawerHeader>
|
||||||
<Divider />
|
<Divider />
|
||||||
<MobileNavigationSidebar
|
{showOnlyAdminMenu ? (
|
||||||
onClick={toggleDrawer}
|
<AdminMobileNavigation onClick={onClick} />
|
||||||
NewInUnleash={NewInUnleash}
|
) : (
|
||||||
/>
|
<MobileNavigationSidebar
|
||||||
|
onClick={toggleDrawer}
|
||||||
|
NewInUnleash={NewInUnleash}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
|
@ -118,11 +118,7 @@ const Header = () => {
|
|||||||
<MenuIcon />
|
<MenuIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<DrawerMenu
|
<DrawerMenu open={openDrawer} toggleDrawer={toggleDrawer} />
|
||||||
links={uiConfig.links}
|
|
||||||
open={openDrawer}
|
|
||||||
toggleDrawer={toggleDrawer}
|
|
||||||
/>
|
|
||||||
<StyledUserContainer>
|
<StyledUserContainer>
|
||||||
<UserProfile />
|
<UserProfile />
|
||||||
</StyledUserContainer>
|
</StyledUserContainer>
|
||||||
|
Loading…
Reference in New Issue
Block a user