feat: activate and disable objects (#2194)
Closes #2144, Closes #2148, Closes #2154
This commit is contained in:
@ -1,15 +1,35 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { MetadataObject } from '../types/MetadataObject';
|
||||||
|
|
||||||
import { activeMetadataObjectsSelector } from '../states/selectors/activeMetadataObjectsSelector';
|
import { useFindManyMetadataObjects } from './useFindManyMetadataObjects';
|
||||||
import { disabledMetadataObjectsSelector } from '../states/selectors/disabledMetadataObjectsSelector';
|
import { useUpdateOneMetadataObject } from './useUpdateOneMetadataObject';
|
||||||
|
|
||||||
export const useObjectMetadata = () => {
|
export const useObjectMetadata = () => {
|
||||||
const activeMetadataObjects = useRecoilValue(activeMetadataObjectsSelector);
|
const { metadataObjects } = useFindManyMetadataObjects();
|
||||||
const disabledMetadataObjects = useRecoilValue(
|
|
||||||
disabledMetadataObjectsSelector,
|
const activeMetadataObjects = metadataObjects.filter(
|
||||||
|
({ isActive }) => isActive,
|
||||||
|
);
|
||||||
|
const disabledMetadataObjects = metadataObjects.filter(
|
||||||
|
({ isActive }) => !isActive,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { updateOneMetadataObject } = useUpdateOneMetadataObject();
|
||||||
|
|
||||||
|
const activateObject = (metadataObject: MetadataObject) =>
|
||||||
|
updateOneMetadataObject({
|
||||||
|
idToUpdate: metadataObject.id,
|
||||||
|
updatePayload: { isActive: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const disableObject = (metadataObject: MetadataObject) =>
|
||||||
|
updateOneMetadataObject({
|
||||||
|
idToUpdate: metadataObject.id,
|
||||||
|
updatePayload: { isActive: false },
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
activateObject,
|
||||||
|
disableObject,
|
||||||
activeObjects: activeMetadataObjects,
|
activeObjects: activeMetadataObjects,
|
||||||
disabledObjects: disabledMetadataObjects,
|
disabledObjects: disabledMetadataObjects,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,81 +1,113 @@
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { IconDotsVertical } from '@/ui/display/icon';
|
import { IconArchive, IconDotsVertical, IconPencil } from '@/ui/display/icon';
|
||||||
import { Tag } from '@/ui/display/tag/components/Tag';
|
import { Tag } from '@/ui/display/tag/components/Tag';
|
||||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||||
|
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||||
import { useLazyLoadIcon } from '@/ui/input/hooks/useLazyLoadIcon';
|
import { useLazyLoadIcon } from '@/ui/input/hooks/useLazyLoadIcon';
|
||||||
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
|
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||||
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
|
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||||
import { Section } from '@/ui/layout/section/components/Section';
|
import { Section } from '@/ui/layout/section/components/Section';
|
||||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
|
||||||
|
|
||||||
type SettingsAboutSectionProps = {
|
type SettingsAboutSectionProps = {
|
||||||
iconKey?: string;
|
iconKey?: string;
|
||||||
isCustom: boolean;
|
isCustom: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
|
onDisable: () => void;
|
||||||
|
onEdit: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledIconTableCell = styled(TableCell)`
|
const StyledCard = styled.div`
|
||||||
justify-content: center;
|
align-items: center;
|
||||||
padding-right: ${({ theme }) => theme.spacing(1)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledTableRow = styled(TableRow)`
|
|
||||||
background-color: ${({ theme }) => theme.background.secondary};
|
background-color: ${({ theme }) => theme.background.secondary};
|
||||||
border: ${({ theme }) => `1px solid ${theme.border.color.medium}`};
|
border: ${({ theme }) => `1px solid ${theme.border.color.medium}`};
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
|
display: flex;
|
||||||
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
|
padding: ${({ theme }) => theme.spacing(2)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledNameTableCell = styled(TableCell)`
|
const StyledName = styled.div`
|
||||||
color: ${({ theme }) => theme.font.color.primary};
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
|
display: flex;
|
||||||
gap: ${({ theme }) => theme.spacing(2)};
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
|
margin-right: auto;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledTag = styled(Tag)`
|
const StyledTag = styled(Tag)`
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: ${({ theme }) => theme.spacing(4)};
|
height: ${({ theme }) => theme.spacing(6)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledIconDotsVertical = styled(IconDotsVertical)`
|
const dropdownScopeId = 'settings-object-edit-about-menu-dropdown';
|
||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledFlexContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SettingsAboutSection = ({
|
export const SettingsAboutSection = ({
|
||||||
iconKey = '',
|
iconKey = '',
|
||||||
isCustom,
|
isCustom,
|
||||||
name,
|
name,
|
||||||
|
onDisable,
|
||||||
|
onEdit,
|
||||||
}: SettingsAboutSectionProps) => {
|
}: SettingsAboutSectionProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { Icon } = useLazyLoadIcon(iconKey);
|
const { Icon } = useLazyLoadIcon(iconKey);
|
||||||
|
|
||||||
|
const { closeDropdown } = useDropdown({ dropdownScopeId });
|
||||||
|
|
||||||
|
const handleEdit = () => {
|
||||||
|
onEdit();
|
||||||
|
closeDropdown();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDisable = () => {
|
||||||
|
onDisable();
|
||||||
|
closeDropdown();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section>
|
<Section>
|
||||||
<H2Title title="About" description="Manage your object" />
|
<H2Title title="About" description="Manage your object" />
|
||||||
<StyledTableRow>
|
<StyledCard>
|
||||||
<StyledNameTableCell>
|
<StyledName>
|
||||||
{!!Icon && <Icon size={theme.icon.size.md} />}
|
{!!Icon && <Icon size={theme.icon.size.md} />}
|
||||||
{name}
|
{name}
|
||||||
</StyledNameTableCell>
|
</StyledName>
|
||||||
<StyledFlexContainer>
|
{isCustom ? (
|
||||||
<TableCell>
|
<StyledTag color="orange" text="Custom" />
|
||||||
{isCustom ? (
|
) : (
|
||||||
<StyledTag color="orange" text="Custom" />
|
<StyledTag color="blue" text="Standard" />
|
||||||
) : (
|
)}
|
||||||
<StyledTag color="blue" text="Standard" />
|
<DropdownScope dropdownScopeId={dropdownScopeId}>
|
||||||
)}
|
<Dropdown
|
||||||
</TableCell>
|
clickableComponent={
|
||||||
<StyledIconTableCell>
|
<LightIconButton Icon={IconDotsVertical} accent="tertiary" />
|
||||||
<StyledIconDotsVertical
|
}
|
||||||
size={theme.icon.size.md}
|
dropdownComponents={
|
||||||
stroke={theme.icon.stroke.sm}
|
<DropdownMenu width="160px">
|
||||||
/>
|
<DropdownMenuItemsContainer>
|
||||||
</StyledIconTableCell>
|
<MenuItem
|
||||||
</StyledFlexContainer>
|
text="Edit"
|
||||||
</StyledTableRow>
|
LeftIcon={IconPencil}
|
||||||
|
onClick={handleEdit}
|
||||||
|
/>
|
||||||
|
<MenuItem
|
||||||
|
text="Disable"
|
||||||
|
LeftIcon={IconArchive}
|
||||||
|
onClick={handleDisable}
|
||||||
|
/>
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
</DropdownMenu>
|
||||||
|
}
|
||||||
|
dropdownHotkeyScope={{
|
||||||
|
scope: dropdownScopeId,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DropdownScope>
|
||||||
|
</StyledCard>
|
||||||
</Section>
|
</Section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,24 +4,37 @@ import { IconArchiveOff } from '@/ui/input/constants/icons';
|
|||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||||
|
|
||||||
type SettingsObjectDisabledMenuDropDownProps = {
|
type SettingsObjectDisabledMenuDropDownProps = {
|
||||||
scopeKey: string;
|
scopeKey: string;
|
||||||
handleActivate: () => void;
|
onActivate: () => void;
|
||||||
handleErase: () => void;
|
onErase: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SettingsObjectDisabledMenuDropDown = ({
|
export const SettingsObjectDisabledMenuDropDown = ({
|
||||||
scopeKey,
|
scopeKey,
|
||||||
handleActivate,
|
onActivate,
|
||||||
handleErase,
|
onErase,
|
||||||
}: SettingsObjectDisabledMenuDropDownProps) => {
|
}: SettingsObjectDisabledMenuDropDownProps) => {
|
||||||
|
const dropdownScopeId = scopeKey + '-settings-object-disabled-menu-dropdown';
|
||||||
|
|
||||||
|
const { closeDropdown } = useDropdown({ dropdownScopeId });
|
||||||
|
|
||||||
|
const handleActivate = () => {
|
||||||
|
onActivate();
|
||||||
|
closeDropdown();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleErase = () => {
|
||||||
|
onErase();
|
||||||
|
closeDropdown();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownScope
|
<DropdownScope dropdownScopeId={dropdownScopeId}>
|
||||||
dropdownScopeId={scopeKey + '-settings-object-disabled-menu-dropdown'}
|
|
||||||
>
|
|
||||||
<Dropdown
|
<Dropdown
|
||||||
clickableComponent={
|
clickableComponent={
|
||||||
<LightIconButton Icon={IconDotsVertical} accent="tertiary" />
|
<LightIconButton Icon={IconDotsVertical} accent="tertiary" />
|
||||||
@ -44,7 +57,7 @@ export const SettingsObjectDisabledMenuDropDown = ({
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
}
|
}
|
||||||
dropdownHotkeyScope={{
|
dropdownHotkeyScope={{
|
||||||
scope: scopeKey + '-settings-object-disabled-menu-dropdown',
|
scope: dropdownScopeId,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</DropdownScope>
|
</DropdownScope>
|
||||||
|
|||||||
@ -23,8 +23,8 @@ const meta: Meta<typeof SettingsObjectDisabledMenuDropDown> = {
|
|||||||
component: SettingsObjectDisabledMenuDropDown,
|
component: SettingsObjectDisabledMenuDropDown,
|
||||||
args: {
|
args: {
|
||||||
scopeKey: 'settings-object-disabled-menu-dropdown',
|
scopeKey: 'settings-object-disabled-menu-dropdown',
|
||||||
handleActivate: handleActivateMockFunction,
|
onActivate: handleActivateMockFunction,
|
||||||
handleErase: handleEraseMockFunction,
|
onErase: handleEraseMockFunction,
|
||||||
},
|
},
|
||||||
decorators: [ComponentDecorator, ClearMocksDecorator],
|
decorators: [ComponentDecorator, ClearMocksDecorator],
|
||||||
parameters: {
|
parameters: {
|
||||||
|
|||||||
@ -30,7 +30,7 @@ export const SettingsObjectDetail = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { pluralObjectName = '' } = useParams();
|
const { pluralObjectName = '' } = useParams();
|
||||||
const { activeObjects } = useObjectMetadata();
|
const { activeObjects, disableObject } = useObjectMetadata();
|
||||||
const activeObject = activeObjects.find(
|
const activeObject = activeObjects.find(
|
||||||
(activeObject) => activeObject.namePlural === pluralObjectName,
|
(activeObject) => activeObject.namePlural === pluralObjectName,
|
||||||
);
|
);
|
||||||
@ -62,6 +62,13 @@ export const SettingsObjectDetail = () => {
|
|||||||
iconKey={activeObject.icon ?? undefined}
|
iconKey={activeObject.icon ?? undefined}
|
||||||
name={activeObject.labelPlural || ''}
|
name={activeObject.labelPlural || ''}
|
||||||
isCustom={activeObject.isCustom}
|
isCustom={activeObject.isCustom}
|
||||||
|
onDisable={() => {
|
||||||
|
disableObject(activeObject);
|
||||||
|
navigate('/settings/objects');
|
||||||
|
}}
|
||||||
|
onEdit={() =>
|
||||||
|
navigate(`/settings/objects/${pluralObjectName}/edit`)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Section>
|
<Section>
|
||||||
@ -76,15 +83,17 @@ export const SettingsObjectDetail = () => {
|
|||||||
<TableHeader>Data type</TableHeader>
|
<TableHeader>Data type</TableHeader>
|
||||||
<TableHeader></TableHeader>
|
<TableHeader></TableHeader>
|
||||||
</StyledObjectFieldTableRow>
|
</StyledObjectFieldTableRow>
|
||||||
<TableSection title="Active">
|
{!!activeFields?.length && (
|
||||||
{activeFields?.map((fieldItem) => (
|
<TableSection title="Active">
|
||||||
<SettingsObjectFieldItemTableRow
|
{activeFields.map((fieldItem) => (
|
||||||
key={fieldItem.id}
|
<SettingsObjectFieldItemTableRow
|
||||||
fieldItem={fieldItem}
|
key={fieldItem.id}
|
||||||
ActionIcon={IconDotsVertical}
|
fieldItem={fieldItem}
|
||||||
/>
|
ActionIcon={IconDotsVertical}
|
||||||
))}
|
/>
|
||||||
</TableSection>
|
))}
|
||||||
|
</TableSection>
|
||||||
|
)}
|
||||||
{!!disabledFields?.length && (
|
{!!disabledFields?.length && (
|
||||||
<TableSection isInitiallyExpanded={false} title="Disabled">
|
<TableSection isInitiallyExpanded={false} title="Disabled">
|
||||||
{disabledFields.map((fieldItem) => (
|
{disabledFields.map((fieldItem) => (
|
||||||
|
|||||||
@ -17,7 +17,7 @@ export const SettingsObjectEdit = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { pluralObjectName = '' } = useParams();
|
const { pluralObjectName = '' } = useParams();
|
||||||
const { activeObjects } = useObjectMetadata();
|
const { activeObjects, disableObject } = useObjectMetadata();
|
||||||
const activeObject = activeObjects.find(
|
const activeObject = activeObjects.find(
|
||||||
(activeObject) => activeObject.namePlural === pluralObjectName,
|
(activeObject) => activeObject.namePlural === pluralObjectName,
|
||||||
);
|
);
|
||||||
@ -52,17 +52,20 @@ export const SettingsObjectEdit = () => {
|
|||||||
pluralName={activeObject.labelPlural}
|
pluralName={activeObject.labelPlural}
|
||||||
description={activeObject.description ?? undefined}
|
description={activeObject.description ?? undefined}
|
||||||
/>
|
/>
|
||||||
|
<Section>
|
||||||
|
<H2Title title="Danger zone" description="Disable object" />
|
||||||
|
<Button
|
||||||
|
Icon={IconArchive}
|
||||||
|
title="Disable"
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
disableObject(activeObject);
|
||||||
|
navigate('/settings/objects');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Section>
|
|
||||||
<H2Title title="Danger zone" description="Disable object" />
|
|
||||||
<Button
|
|
||||||
Icon={IconArchive}
|
|
||||||
title="Disable"
|
|
||||||
size="small"
|
|
||||||
onClick={() => undefined}
|
|
||||||
/>
|
|
||||||
</Section>
|
|
||||||
</SettingsPageContainer>
|
</SettingsPageContainer>
|
||||||
</SubMenuTopBarContainer>
|
</SubMenuTopBarContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -87,15 +87,17 @@ export const SettingsObjectNewFieldStep1 = () => {
|
|||||||
<TableHeader>Data type</TableHeader>
|
<TableHeader>Data type</TableHeader>
|
||||||
<TableHeader></TableHeader>
|
<TableHeader></TableHeader>
|
||||||
</StyledObjectFieldTableRow>
|
</StyledObjectFieldTableRow>
|
||||||
<TableSection isInitiallyExpanded={false} title="Active">
|
{!!activeFields?.length && (
|
||||||
{activeFields?.map((fieldItem) => (
|
<TableSection isInitiallyExpanded={false} title="Active">
|
||||||
<SettingsObjectFieldItemTableRow
|
{activeFields.map((fieldItem) => (
|
||||||
key={fieldItem.id}
|
<SettingsObjectFieldItemTableRow
|
||||||
fieldItem={fieldItem}
|
key={fieldItem.id}
|
||||||
ActionIcon={IconMinus}
|
fieldItem={fieldItem}
|
||||||
/>
|
ActionIcon={IconMinus}
|
||||||
))}
|
/>
|
||||||
</TableSection>
|
))}
|
||||||
|
</TableSection>
|
||||||
|
)}
|
||||||
{!!disabledFields?.length && (
|
{!!disabledFields?.length && (
|
||||||
<TableSection title="Disabled">
|
<TableSection title="Disabled">
|
||||||
{disabledFields.map((fieldItem) => (
|
{disabledFields.map((fieldItem) => (
|
||||||
|
|||||||
@ -33,7 +33,8 @@ export const SettingsObjects = () => {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { activeObjects, disabledObjects } = useObjectMetadata();
|
const { activateObject, activeObjects, disabledObjects } =
|
||||||
|
useObjectMetadata();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||||
@ -62,23 +63,25 @@ export const SettingsObjects = () => {
|
|||||||
<TableHeader align="right">Instances</TableHeader>
|
<TableHeader align="right">Instances</TableHeader>
|
||||||
<TableHeader></TableHeader>
|
<TableHeader></TableHeader>
|
||||||
</StyledObjectTableRow>
|
</StyledObjectTableRow>
|
||||||
<TableSection title="Active">
|
{!!activeObjects.length && (
|
||||||
{activeObjects.map((objectItem) => (
|
<TableSection title="Active">
|
||||||
<SettingsObjectItemTableRow
|
{activeObjects.map((objectItem) => (
|
||||||
key={objectItem.namePlural}
|
<SettingsObjectItemTableRow
|
||||||
objectItem={objectItem}
|
key={objectItem.namePlural}
|
||||||
action={
|
objectItem={objectItem}
|
||||||
<StyledIconChevronRight
|
action={
|
||||||
size={theme.icon.size.md}
|
<StyledIconChevronRight
|
||||||
stroke={theme.icon.stroke.sm}
|
size={theme.icon.size.md}
|
||||||
/>
|
stroke={theme.icon.stroke.sm}
|
||||||
}
|
/>
|
||||||
onClick={() =>
|
}
|
||||||
navigate(`/settings/objects/${objectItem.namePlural}`)
|
onClick={() =>
|
||||||
}
|
navigate(`/settings/objects/${objectItem.namePlural}`)
|
||||||
/>
|
}
|
||||||
))}
|
/>
|
||||||
</TableSection>
|
))}
|
||||||
|
</TableSection>
|
||||||
|
)}
|
||||||
{!!disabledObjects.length && (
|
{!!disabledObjects.length && (
|
||||||
<TableSection title="Disabled">
|
<TableSection title="Disabled">
|
||||||
{disabledObjects.map((objectItem) => (
|
{disabledObjects.map((objectItem) => (
|
||||||
@ -88,8 +91,8 @@ export const SettingsObjects = () => {
|
|||||||
action={
|
action={
|
||||||
<SettingsObjectDisabledMenuDropDown
|
<SettingsObjectDisabledMenuDropDown
|
||||||
scopeKey={objectItem.namePlural}
|
scopeKey={objectItem.namePlural}
|
||||||
handleActivate={() => undefined}
|
onActivate={() => activateObject(objectItem)}
|
||||||
handleErase={() => undefined}
|
onErase={() => undefined}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user