mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-04-23 14:08:01 +00:00
feat: initial Pezkuwi Apps rebrand from polkadot-apps
Rebranded terminology: - Polkadot → Pezkuwi - Kusama → Dicle - Westend → Zagros - Rococo → PezkuwiChain - Substrate → Bizinikiwi - parachain → teyrchain Custom logos with Kurdistan brand colors (#e6007a → #86e62a): - bizinikiwi-hexagon.svg - sora-bizinikiwi.svg - hezscanner.svg - heztreasury.svg - pezkuwiscan.svg - pezkuwistats.svg - pezkuwiassembly.svg - pezkuwiholic.svg
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { RuntimeVersion } from '@pezkuwi/types/interfaces';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { ChainImg, Icon, styled } from '@pezkuwi/react-components';
|
||||
import { useApi, useCall, useIpfs, useToggle } from '@pezkuwi/react-hooks';
|
||||
import { BestNumber, Chain } from '@pezkuwi/react-query';
|
||||
|
||||
import Endpoints from '../Endpoints/index.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function ChainInfo ({ className }: Props): React.ReactElement<Props> {
|
||||
const { api, isApiReady } = useApi();
|
||||
const runtimeVersion = useCall<RuntimeVersion>(isApiReady && api.rpc.state.subscribeRuntimeVersion);
|
||||
const { ipnsChain } = useIpfs();
|
||||
const [isEndpointsVisible, toggleEndpoints] = useToggle();
|
||||
const canToggle = !ipnsChain;
|
||||
|
||||
return (
|
||||
<StyledDiv className={className}>
|
||||
<div
|
||||
className={`apps--SideBar-logo-inner${canToggle ? ' isClickable' : ''} highlight--color-contrast`}
|
||||
onClick={toggleEndpoints}
|
||||
>
|
||||
<ChainImg />
|
||||
<div className='info media--1000'>
|
||||
<Chain className='chain' />
|
||||
{runtimeVersion && (
|
||||
<div className='runtimeVersion'>{runtimeVersion.specName.toString()}/{runtimeVersion.specVersion.toNumber()}</div>
|
||||
)}
|
||||
<BestNumber
|
||||
className='bestNumber'
|
||||
label='#'
|
||||
/>
|
||||
</div>
|
||||
{canToggle && (
|
||||
<Icon
|
||||
className='dropdown'
|
||||
icon={isEndpointsVisible ? 'caret-right' : 'caret-down'}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{isEndpointsVisible && (
|
||||
<Endpoints onClose={toggleEndpoints} />
|
||||
)}
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
box-sizing: border-box;
|
||||
padding: 0.5rem 1rem 0.5rem 0;
|
||||
margin: 0;
|
||||
|
||||
.apps--SideBar-logo-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&.isClickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ui--ChainImg {
|
||||
height: 3rem;
|
||||
margin-right: 0.5rem;
|
||||
width: 3rem;
|
||||
}
|
||||
|
||||
.ui--Icon.dropdown,
|
||||
> div.info {
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.ui--Icon.dropdown {
|
||||
flex: 0;
|
||||
margin: 0;
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
font-size: var(--font-size-tiny);
|
||||
line-height: 1.2;
|
||||
padding-right: 0.5rem;
|
||||
text-align: right;
|
||||
|
||||
.chain {
|
||||
font-size: var(--font-size-small);
|
||||
max-width: 16rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.runtimeVersion {
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(ChainInfo);
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Group } from './types.js';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Icon, styled } from '@pezkuwi/react-components';
|
||||
|
||||
import Item from './Item.js';
|
||||
|
||||
interface Props extends Group {
|
||||
className?: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
const SHA_COL = 'rgba(34, 36, 38, 0.12)';
|
||||
const SHA_OFF = '5px';
|
||||
|
||||
function Grouping ({ className = '', isActive, name, routes }: Props): React.ReactElement<Props> {
|
||||
if (routes.length === 1 && routes[0].group === 'settings') {
|
||||
return (
|
||||
<Item
|
||||
className={isActive ? 'isActive' : ''}
|
||||
classNameText='smallHide'
|
||||
isToplevel
|
||||
route={routes[0]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledLi className={`${className} ${isActive ? 'isActive' : ''}`}>
|
||||
<div className={`groupHdr ${!isActive ? 'highlight--color-contrast' : ''}`}>
|
||||
<span className='smallHide'>{name}</span>
|
||||
<Icon
|
||||
className='smallShow'
|
||||
icon={routes[0].icon}
|
||||
/>
|
||||
<Icon icon='caret-down' />
|
||||
</div>
|
||||
<ul className='groupMenu'>
|
||||
{routes.map((route): React.ReactNode => (
|
||||
<Item
|
||||
key={route.name}
|
||||
route={route}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</StyledLi>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledLi = styled.li`
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
.groupHdr {
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.857rem 1.375rem;
|
||||
font-weight: var(--font-weight-normal);
|
||||
line-height: 1.214rem;
|
||||
|
||||
> .ui--Icon {
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.isActive .groupHdr {
|
||||
background-color: var(--bg-tabs);
|
||||
font-weight: var(--font-weight-normal);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.groupMenu {
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: 0 ${SHA_OFF} ${SHA_OFF} -${SHA_OFF} ${SHA_COL}, ${SHA_OFF} 0 ${SHA_OFF} -${SHA_OFF} ${SHA_COL}, -${SHA_OFF} 0 ${SHA_OFF} -${SHA_OFF} ${SHA_COL};
|
||||
display: none;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: 2.9rem;
|
||||
z-index: 250;
|
||||
|
||||
> li {
|
||||
z-index: 1;
|
||||
|
||||
a {
|
||||
padding-right: 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
bottom: 0;
|
||||
content: ' ';
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.groupHdr {
|
||||
box-shadow: 0px 4px 37px rgba(0, 0, 0, 0.08);
|
||||
padding-bottom: 2rem;
|
||||
margin-bottom: -2rem;
|
||||
}
|
||||
|
||||
.groupMenu {
|
||||
display: block;
|
||||
|
||||
> li:hover {
|
||||
background: var(--bg-menu-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Grouping);
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ItemRoute } from './types.js';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Badge, Icon, styled } from '@pezkuwi/react-components';
|
||||
import { useToggle } from '@pezkuwi/react-hooks';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
classNameText?: string;
|
||||
isLink?: boolean;
|
||||
isToplevel?: boolean;
|
||||
route: ItemRoute;
|
||||
}
|
||||
|
||||
const DUMMY_COUNTER = () => 0;
|
||||
|
||||
function Item ({ className = '', classNameText, isLink, isToplevel, route: { Modal, href, icon, name, text, useCounter = DUMMY_COUNTER } }: Props): React.ReactElement<Props> {
|
||||
const [isModalVisible, toggleModal] = useToggle();
|
||||
const count = useCounter();
|
||||
|
||||
return (
|
||||
<StyledLi className={`${className} ui--MenuItem ${count ? 'withCounter' : ''} ${isLink ? 'isLink' : ''} ${isToplevel ? 'topLevel highlight--color-contrast' : ''}`}>
|
||||
<a
|
||||
href={Modal ? undefined : (href || `#/${name}`)}
|
||||
onClick={Modal ? toggleModal : undefined}
|
||||
rel='noopener noreferrer'
|
||||
target={href ? '_blank' : undefined}
|
||||
>
|
||||
<Icon icon={icon} />
|
||||
<span className={classNameText}>{text}</span>
|
||||
{!!count && (
|
||||
<Badge
|
||||
color='white'
|
||||
info={count}
|
||||
/>
|
||||
)}
|
||||
</a>
|
||||
{Modal && isModalVisible && (
|
||||
<Modal onClose={toggleModal} />
|
||||
)}
|
||||
</StyledLi>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledLi = styled.li`
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
|
||||
&.topLevel {
|
||||
font-weight: var(--font-weight-normal);
|
||||
line-height: 1.214rem;
|
||||
border-radius: 0.15rem;
|
||||
|
||||
a {
|
||||
padding: 0.857rem 0.857em 0.857rem 1rem;
|
||||
line-height: 1.214rem;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
&.isActive.highlight--color-contrast {
|
||||
font-weight: var(--font-weight-normal);
|
||||
color: var(--color-text);
|
||||
|
||||
a {
|
||||
background-color: var(--bg-tabs);
|
||||
}
|
||||
}
|
||||
|
||||
&.isActive {
|
||||
border-radius: 0.15rem 0.15rem 0 0;
|
||||
|
||||
a {
|
||||
padding: 0.857rem 1.429rem 0.857rem;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&&.withCounter a {
|
||||
padding-right: 3.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.ui--Badge {
|
||||
top: 0.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
&&.withCounter a {
|
||||
padding-right: 3.2rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit !important;
|
||||
display: block;
|
||||
padding: 0.5rem 1.15rem 0.57rem;
|
||||
text-decoration: none;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
.ui--Badge {
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
}
|
||||
|
||||
.ui--Icon {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Item);
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BareProps as Props } from '@pezkuwi/react-components/types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { packageInfo } from '@pezkuwi/apps-config';
|
||||
import { styled } from '@pezkuwi/react-components';
|
||||
import { useApi } from '@pezkuwi/react-hooks';
|
||||
import { NodeName, NodeVersion } from '@pezkuwi/react-query';
|
||||
|
||||
const appsVersion = `apps v${packageInfo.version.replace('-x', '')}`;
|
||||
|
||||
function NodeInfo ({ className = '' }: Props): React.ReactElement<Props> {
|
||||
const { api, isApiReady } = useApi();
|
||||
|
||||
return (
|
||||
<StyledDiv className={`${className} media--1400 highlight--color-contrast ui--NodeInfo`}>
|
||||
{isApiReady && (
|
||||
<div className='node'>
|
||||
<NodeName />
|
||||
<NodeVersion label='v' />
|
||||
</div>
|
||||
)}
|
||||
<div>{api.libraryInfo.replace('@pezkuwi/', '')}</div>
|
||||
<div>{appsVersion}</div>
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
background: transparent;
|
||||
font-size: var(--font-size-tiny);
|
||||
line-height: 1.2;
|
||||
padding: 0 0 0 1rem;
|
||||
text-align: right;
|
||||
|
||||
> div {
|
||||
margin-bottom: -0.125em;
|
||||
|
||||
> div {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(NodeInfo);
|
||||
@@ -0,0 +1,258 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Route, Routes } from '@pezkuwi/apps-routing/types';
|
||||
import type { ApiProps } from '@pezkuwi/react-api/types';
|
||||
import type { AccountId } from '@pezkuwi/types/interfaces';
|
||||
import type { Group, Groups, ItemRoute } from './types.js';
|
||||
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import createRoutes from '@pezkuwi/apps-routing';
|
||||
import { styled } from '@pezkuwi/react-components';
|
||||
import { useAccounts, useApi, useCall, useTeleport } from '@pezkuwi/react-hooks';
|
||||
|
||||
import { findMissingApis } from '../endpoint.js';
|
||||
import { useTranslation } from '../translate.js';
|
||||
import ChainInfo from './ChainInfo.js';
|
||||
import Grouping from './Grouping.js';
|
||||
import Item from './Item.js';
|
||||
import NodeInfo from './NodeInfo.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function createExternals (t: (key: string, optionsOrText?: string | { replace: Record<string, unknown> }, options?: { ns: string }) => string): ItemRoute[] {
|
||||
return [
|
||||
{
|
||||
href: 'https://github.com/pezkuwi-js/apps',
|
||||
icon: 'code-branch',
|
||||
name: 'github',
|
||||
text: t('nav.github', 'GitHub', { ns: 'apps-routing' })
|
||||
},
|
||||
{
|
||||
href: 'https://wiki.pezkuwi.network',
|
||||
icon: 'book',
|
||||
name: 'wiki',
|
||||
text: t('nav.wiki', 'Wiki', { ns: 'apps-routing' })
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function checkVisible ({ api, isApiConnected, isApiReady, isDevelopment: isApiDevelopment }: ApiProps, allowTeleport: boolean, hasAccounts: boolean, hasSudo: boolean, { isDevelopment, isHidden, needsAccounts, needsApi, needsApiCheck, needsApiInstances, needsSudo, needsTeleport }: Route['display']): boolean {
|
||||
if (isHidden) {
|
||||
return false;
|
||||
} else if (needsAccounts && !hasAccounts) {
|
||||
return false;
|
||||
} else if (!needsApi) {
|
||||
return true;
|
||||
} else if (!isApiReady || !isApiConnected) {
|
||||
return false;
|
||||
} else if (needsSudo && !hasSudo) {
|
||||
return false;
|
||||
} else if (needsTeleport && !allowTeleport) {
|
||||
return false;
|
||||
} else if (!isApiDevelopment && isDevelopment) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return findMissingApis(api, needsApi, needsApiInstances, needsApiCheck).length === 0;
|
||||
}
|
||||
|
||||
function extractGroups (routing: Routes, groupNames: Record<string, string>, apiProps: ApiProps, allowTeleport: boolean, hasAccounts: boolean, hasSudo: boolean): Group[] {
|
||||
return Object
|
||||
.values(
|
||||
routing.reduce((all: Groups, route): Groups => {
|
||||
if (!all[route.group]) {
|
||||
all[route.group] = {
|
||||
name: groupNames[route.group],
|
||||
routes: [route]
|
||||
};
|
||||
} else {
|
||||
all[route.group].routes.push(route);
|
||||
}
|
||||
|
||||
return all;
|
||||
}, {})
|
||||
)
|
||||
.map(({ name, routes }): Group => ({
|
||||
name,
|
||||
routes: routes.filter(({ display }) =>
|
||||
checkVisible(apiProps, allowTeleport, hasAccounts, hasSudo, display)
|
||||
)
|
||||
}))
|
||||
.filter(({ routes }) => routes.length);
|
||||
}
|
||||
|
||||
function Menu ({ className = '' }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { allAccounts, hasAccounts } = useAccounts();
|
||||
const apiProps = useApi();
|
||||
const { allowTeleport } = useTeleport();
|
||||
const sudoKey = useCall<AccountId>(apiProps.isApiReady && apiProps.api.query.sudo?.key);
|
||||
const location = useLocation();
|
||||
|
||||
const externalRef = useRef(createExternals(t));
|
||||
const routeRef = useRef(createRoutes(t));
|
||||
|
||||
const groupRef = useRef({
|
||||
accounts: t('Accounts'),
|
||||
developer: t('Developer'),
|
||||
files: t('Files'),
|
||||
governance: t('Governance'),
|
||||
network: t('Network'),
|
||||
settings: t('Settings')
|
||||
});
|
||||
|
||||
const hasSudo = useMemo(
|
||||
() => !!sudoKey && allAccounts.some((a) => sudoKey.eq(a)),
|
||||
[allAccounts, sudoKey]
|
||||
);
|
||||
|
||||
const visibleGroups = useMemo(
|
||||
() => extractGroups(routeRef.current, groupRef.current, apiProps, allowTeleport, hasAccounts, hasSudo),
|
||||
[allowTeleport, apiProps, hasAccounts, hasSudo]
|
||||
);
|
||||
|
||||
const activeRoute = useMemo(
|
||||
() => routeRef.current.find(({ name }) =>
|
||||
location.pathname.startsWith(`/${name}`)
|
||||
) || null,
|
||||
[location]
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledDiv className={`${className}${(!apiProps.isApiReady || !apiProps.isApiConnected) ? ' isLoading' : ''} highlight--bg`}>
|
||||
<div className='menuContainer'>
|
||||
<div className='menuSection'>
|
||||
<ChainInfo />
|
||||
<ul className='menuItems'>
|
||||
{visibleGroups.map(({ name, routes }): React.ReactNode => (
|
||||
<Grouping
|
||||
isActive={!!activeRoute && activeRoute.group === name.toLowerCase()}
|
||||
key={name}
|
||||
name={name}
|
||||
routes={routes}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className='menuSection media--1200'>
|
||||
<ul className='menuItems'>
|
||||
{externalRef.current.map((route): React.ReactNode => (
|
||||
<Item
|
||||
isLink
|
||||
isToplevel
|
||||
key={route.name}
|
||||
route={route}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<NodeInfo className='media--1400' />
|
||||
</div>
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
z-index: 220;
|
||||
position: relative;
|
||||
|
||||
.smallShow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& .menuContainer {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 1.5rem;
|
||||
width: 100%;
|
||||
max-width: var(--width-full);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
&.isLoading {
|
||||
background: #999 !important;
|
||||
|
||||
.menuActive {
|
||||
background: var(--bg-page);
|
||||
}
|
||||
|
||||
&:before {
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
.menuItems {
|
||||
filter: grayscale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.menuSection {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.menuActive {
|
||||
background: var(--bg-tabs);
|
||||
border-bottom: none;
|
||||
border-radius: 0.25rem 0.25rem 0 0;
|
||||
color: var(--color-text);
|
||||
padding: 1rem 1.5rem;
|
||||
margin: 0 1rem -1px;
|
||||
z-index: 1;
|
||||
|
||||
.ui--Icon {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.menuItems {
|
||||
flex: 1 1;
|
||||
list-style: none;
|
||||
margin: 0 1rem 0 0;
|
||||
padding: 0;
|
||||
|
||||
> li {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
> li + li {
|
||||
margin-left: 0.375rem
|
||||
}
|
||||
}
|
||||
|
||||
.ui--NodeInfo {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
.groupHdr {
|
||||
padding: 0.857rem 0.75rem;
|
||||
}
|
||||
|
||||
.smallShow {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
.smallHide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.menuItems {
|
||||
margin-right: 0;
|
||||
|
||||
> li + li {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Menu);
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { IconName } from '@fortawesome/fontawesome-svg-core';
|
||||
import type React from 'react';
|
||||
import type { Routes } from '@pezkuwi/apps-routing/types';
|
||||
|
||||
export interface ItemRoute {
|
||||
Modal?: React.ComponentType<any>;
|
||||
href?: string;
|
||||
icon: IconName;
|
||||
name: string;
|
||||
text: string;
|
||||
useCounter?: () => number | string | null;
|
||||
}
|
||||
|
||||
export interface Group {
|
||||
name: string;
|
||||
routes: Routes;
|
||||
}
|
||||
|
||||
export type Groups = Record<string, Group>;
|
||||
Reference in New Issue
Block a user