chore: re-organize fragments indexed nfa view
This commit is contained in:
parent
36223a7620
commit
698238c9b9
|
|
@ -1,320 +0,0 @@
|
|||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { App } from '@/app.context';
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
Icon,
|
||||
IconName,
|
||||
Menu,
|
||||
NFAIcon,
|
||||
NFAPreview,
|
||||
ResolvedAddress,
|
||||
Text,
|
||||
} from '@/components';
|
||||
import { env } from '@/constants';
|
||||
import { FleekERC721 } from '@/integrations/ethereum/contracts';
|
||||
import { forwardStyledRef } from '@/theme';
|
||||
import { AppLog, getDate, getRepositoryFromURL } from '@/utils';
|
||||
import { parseNumberToHexColor } from '@/utils/color';
|
||||
|
||||
import { IndexedNFA } from '../indexed-nfa.context';
|
||||
import { IndexedNFAStyles as S } from '../indexed-nfa.styles';
|
||||
import { Tab, TabContainer } from './tabs';
|
||||
|
||||
const Preview: React.FC = () => {
|
||||
const { nfa } = IndexedNFA.useContext();
|
||||
|
||||
const color = useMemo(
|
||||
() => `#${parseNumberToHexColor(nfa.color ?? '')}`,
|
||||
[nfa]
|
||||
);
|
||||
|
||||
return (
|
||||
<NFAPreview
|
||||
color={color}
|
||||
logo={nfa.logo}
|
||||
ens={nfa.ENS}
|
||||
name={nfa.name}
|
||||
size="100%"
|
||||
css={{
|
||||
borderRadius: '$lg',
|
||||
border: '1px solid $slate6',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type BadgeProps = {
|
||||
verified: boolean;
|
||||
};
|
||||
|
||||
const Badge: React.FC<BadgeProps> = ({ verified }: BadgeProps) => {
|
||||
const text = useMemo(
|
||||
() => (verified ? 'Verified' : 'Unverified'),
|
||||
[verified]
|
||||
);
|
||||
|
||||
const icon = useMemo(() => (verified ? 'verified' : 'error'), [verified]);
|
||||
const color = useMemo(() => (verified ? '$green10' : '$red10'), [verified]);
|
||||
return (
|
||||
<S.Aside.Header.Badge verified={verified}>
|
||||
<Icon name={icon} css={{ color: color }} />
|
||||
{text}
|
||||
</S.Aside.Header.Badge>
|
||||
);
|
||||
};
|
||||
|
||||
const Header: React.FC = () => {
|
||||
const { nfa } = IndexedNFA.useContext();
|
||||
|
||||
return (
|
||||
<S.Aside.Header.Wrapper>
|
||||
<S.Aside.Header.Container>
|
||||
<S.Aside.Header.Header>{nfa.name}</S.Aside.Header.Header>
|
||||
<Badge verified={nfa.verified} />
|
||||
</S.Aside.Header.Container>
|
||||
|
||||
<Flex css={{ gap: '$1h' }}>
|
||||
<NFAIcon image={nfa.logo} color={'white'} />
|
||||
<ResolvedAddress>{nfa.owner.id}</ResolvedAddress>
|
||||
</Flex>
|
||||
</S.Aside.Header.Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
type HeaderDataProps = {
|
||||
label: string;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const HeaderData: React.FC<HeaderDataProps> = ({
|
||||
label,
|
||||
children,
|
||||
}: HeaderDataProps) => (
|
||||
<Flex css={{ gap: '$2', fontSize: '14px', fontWeight: '400' }}>
|
||||
<Text css={{ color: '$slate11' }}>{label}</Text>
|
||||
<Text css={{ color: '$slate12' }}>{children}</Text>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
const NFAInfo: React.FC = () => {
|
||||
const { nfa } = IndexedNFA.useContext();
|
||||
return (
|
||||
<Flex css={{ alignItems: 'center', gap: '$2h' }}>
|
||||
<HeaderData label="Hosted NFAs">
|
||||
{nfa.accessPoints?.length ?? 0}
|
||||
</HeaderData>
|
||||
|
||||
<S.Aside.Divider.Elipse />
|
||||
|
||||
<HeaderData label="Created">{getDate(nfa.createdAt)}</HeaderData>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
type CustomButtonProps = {
|
||||
icon: IconName;
|
||||
};
|
||||
|
||||
const CustomButon = forwardStyledRef<HTMLButtonElement, CustomButtonProps>(
|
||||
({ icon, ...props }, ref) => (
|
||||
<Button
|
||||
ref={ref}
|
||||
{...props}
|
||||
css={{ borderRadius: '0.375rem', padding: '$2', color: 'white' }}
|
||||
>
|
||||
<Icon name={icon} />
|
||||
</Button>
|
||||
)
|
||||
);
|
||||
|
||||
type MenuItemProps = {
|
||||
label: string;
|
||||
iconName: IconName;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
const MenuItem: React.FC<MenuItemProps> = ({
|
||||
label,
|
||||
iconName,
|
||||
onClick,
|
||||
}: MenuItemProps) => {
|
||||
return (
|
||||
<Flex onClick={onClick} css={{ gap: '$2' }}>
|
||||
<Icon name={iconName} />
|
||||
{label}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const ButtonsFragment: React.FC = () => {
|
||||
const { nfa } = IndexedNFA.useContext();
|
||||
|
||||
const handleShareOnClick = (): void => {
|
||||
const location = window.location.href;
|
||||
navigator.clipboard.writeText(location);
|
||||
AppLog.successToast('Link copied to clipboard');
|
||||
};
|
||||
|
||||
const handleShareOpenSeaOnClick = (): void => {
|
||||
window.open(
|
||||
`https://${
|
||||
env.environment === 'development' ? 'testnets' : ''
|
||||
}.opensea.io/assets/${
|
||||
env.environment === 'development' ? 'goerli' : 'ethereum'
|
||||
}/${FleekERC721.address}/${nfa.tokenId}`,
|
||||
'_blank'
|
||||
);
|
||||
};
|
||||
|
||||
const handleShareOnTwitterOnClick = (): void => {
|
||||
window.open(env.twitter.url, '_blank'); //TODO replace with twitter share
|
||||
};
|
||||
|
||||
return (
|
||||
<S.Aside.Button.Container>
|
||||
<Menu.Root>
|
||||
<Menu.Button as={CustomButon} icon={'three-dots'} />
|
||||
<Menu.Items css={{ minWidth: '12rem' }}>
|
||||
<span>
|
||||
<MenuItem
|
||||
label="Open on OpenSea"
|
||||
iconName="opensea"
|
||||
onClick={handleShareOpenSeaOnClick}
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<MenuItem
|
||||
label="Share to Twitter"
|
||||
iconName="twitter"
|
||||
onClick={handleShareOnTwitterOnClick}
|
||||
/>
|
||||
</span>
|
||||
</Menu.Items>
|
||||
</Menu.Root>
|
||||
|
||||
{/* TODO add tooltip to copy link */}
|
||||
<CustomButon icon="share" onClick={handleShareOnClick} />
|
||||
</S.Aside.Button.Container>
|
||||
);
|
||||
};
|
||||
|
||||
const PropertiesFragment: React.FC = () => {
|
||||
const { nfa } = IndexedNFA.useContext();
|
||||
|
||||
const traitsToShow = useMemo(() => {
|
||||
return [
|
||||
[nfa.ENS, 'ENS'],
|
||||
[getRepositoryFromURL(nfa.gitRepository.id), 'Repository'],
|
||||
[10, 'Version'],
|
||||
[nfa.externalURL, 'Domain'],
|
||||
];
|
||||
}, [nfa]);
|
||||
|
||||
return (
|
||||
<Flex css={{ flexDirection: 'column', gap: '$3', height: '336px' }}>
|
||||
{traitsToShow.map(([value, label], index) => (
|
||||
<S.Aside.Overview.Container
|
||||
css={{ gap: '$1', p: '$2h $4' }}
|
||||
key={index}
|
||||
>
|
||||
<S.Aside.Overview.Row.Value>
|
||||
{value || '-'}
|
||||
</S.Aside.Overview.Row.Value>
|
||||
<S.Aside.Overview.Row.Label>{label}</S.Aside.Overview.Row.Label>
|
||||
</S.Aside.Overview.Container>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const OverviewFragment: React.FC = () => {
|
||||
const { nfa } = IndexedNFA.useContext();
|
||||
|
||||
return (
|
||||
<S.Aside.Overview.Container
|
||||
css={{ gap: '$4h', p: '$5 $6', height: '336px' }}
|
||||
>
|
||||
<S.Aside.Overview.Row.Container>
|
||||
<S.Aside.Overview.Row.Label>Token ID</S.Aside.Overview.Row.Label>
|
||||
<S.Aside.Overview.Row.Value>{nfa.tokenId}</S.Aside.Overview.Row.Value>
|
||||
</S.Aside.Overview.Row.Container>
|
||||
<S.Aside.Divider.Line />
|
||||
<S.Aside.Overview.Row.Container>
|
||||
<S.Aside.Overview.Row.Label>Network</S.Aside.Overview.Row.Label>
|
||||
<S.Aside.Overview.Row.Value>Mainnet</S.Aside.Overview.Row.Value>
|
||||
</S.Aside.Overview.Row.Container>
|
||||
<S.Aside.Divider.Line />
|
||||
<S.Aside.Overview.Row.Container>
|
||||
<S.Aside.Overview.Row.Label>Standard</S.Aside.Overview.Row.Label>
|
||||
<S.Aside.Overview.Row.Value>ERC_721</S.Aside.Overview.Row.Value>
|
||||
</S.Aside.Overview.Row.Container>
|
||||
<S.Aside.Divider.Line />
|
||||
<S.Aside.Overview.Row.Container>
|
||||
<S.Aside.Overview.Row.Label>Description</S.Aside.Overview.Row.Label>
|
||||
</S.Aside.Overview.Row.Container>
|
||||
<S.Aside.Overview.Description css={{ overflowY: 'scroll' }}>
|
||||
{nfa.description}
|
||||
</S.Aside.Overview.Description>
|
||||
</S.Aside.Overview.Container>
|
||||
);
|
||||
};
|
||||
|
||||
const TabFragment: React.FC = () => {
|
||||
const [tabSelected, setTabSelected] = useState<number>(0);
|
||||
const handleClick = (index: number): void => {
|
||||
setTabSelected(index);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<TabContainer>
|
||||
{['Overview', 'Properties'].map((label, index) => (
|
||||
<Tab
|
||||
key={index}
|
||||
index={index}
|
||||
label={label}
|
||||
active={index === tabSelected}
|
||||
onTabClick={handleClick}
|
||||
/>
|
||||
))}
|
||||
</TabContainer>
|
||||
{tabSelected === 0 ? <OverviewFragment /> : <PropertiesFragment />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const IndexedNFAAsideFragment: React.FC = () => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [top, setTop] = useState<number>();
|
||||
const { nfa } = IndexedNFA.useContext();
|
||||
|
||||
const { backgroundColor } = App.useContext();
|
||||
const background = `radial-gradient(closest-corner circle at 90% 45%, #${backgroundColor}8c 1% ,#${backgroundColor}57 20%, transparent 40%), radial-gradient(closest-corner circle at 60% 25%, #${backgroundColor} 3%, #${backgroundColor}73 30%, #181818 70%)`;
|
||||
|
||||
useEffect(() => {
|
||||
setTop(ref.current?.getBoundingClientRect().top);
|
||||
}, [ref]);
|
||||
|
||||
return (
|
||||
<S.Aside.Container
|
||||
ref={ref}
|
||||
css={{ top, background, backdropFilter: 'blur(10px)' }}
|
||||
>
|
||||
<Preview />
|
||||
<Header />
|
||||
<NFAInfo />
|
||||
<ButtonsFragment />
|
||||
<Button
|
||||
as={Link}
|
||||
to={`/create-ap/${nfa.tokenId}`}
|
||||
css={{
|
||||
backgroundColor: `#${parseNumberToHexColor(nfa.color)}`,
|
||||
color: 'white',
|
||||
}}
|
||||
>{`Host ${nfa.name} NFA`}</Button>
|
||||
<TabFragment />
|
||||
</S.Aside.Container>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
import { Button, Flex, Icon, IconName, Menu } from '@/components';
|
||||
import { env } from '@/constants';
|
||||
import { FleekERC721 } from '@/integrations/ethereum/contracts';
|
||||
import { forwardStyledRef } from '@/theme';
|
||||
import { AppLog } from '@/utils';
|
||||
|
||||
import { IndexedNFA } from '../../indexed-nfa.context';
|
||||
import { IndexedNFAStyles as S } from '../../indexed-nfa.styles';
|
||||
|
||||
type CustomButtonProps = {
|
||||
icon: IconName;
|
||||
};
|
||||
|
||||
const CustomButon = forwardStyledRef<HTMLButtonElement, CustomButtonProps>(
|
||||
({ icon, ...props }, ref) => (
|
||||
<Button
|
||||
ref={ref}
|
||||
{...props}
|
||||
css={{ borderRadius: '0.375rem', padding: '$2', color: 'white' }}
|
||||
>
|
||||
<Icon name={icon} />
|
||||
</Button>
|
||||
)
|
||||
);
|
||||
|
||||
type MenuItemProps = {
|
||||
label: string;
|
||||
iconName: IconName;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
const MenuItem: React.FC<MenuItemProps> = ({
|
||||
label,
|
||||
iconName,
|
||||
onClick,
|
||||
}: MenuItemProps) => {
|
||||
return (
|
||||
<Flex onClick={onClick} css={{ gap: '$2' }}>
|
||||
<Icon name={iconName} />
|
||||
{label}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const ButtonsFragment: React.FC = () => {
|
||||
const { nfa } = IndexedNFA.useContext();
|
||||
|
||||
const handleShareOnClick = (): void => {
|
||||
const location = window.location.href;
|
||||
navigator.clipboard.writeText(location);
|
||||
AppLog.successToast('Link copied to clipboard');
|
||||
};
|
||||
|
||||
const handleShareOpenSeaOnClick = (): void => {
|
||||
window.open(
|
||||
`https://${
|
||||
env.environment === 'development' ? 'testnets' : ''
|
||||
}.opensea.io/assets/${
|
||||
env.environment === 'development' ? 'goerli' : 'ethereum'
|
||||
}/${FleekERC721.address}/${nfa.tokenId}`,
|
||||
'_blank'
|
||||
);
|
||||
};
|
||||
|
||||
const handleShareOnTwitterOnClick = (): void => {
|
||||
window.open(env.twitter.url, '_blank'); //TODO replace with twitter share
|
||||
};
|
||||
|
||||
return (
|
||||
<S.Aside.Button.Container>
|
||||
<Menu.Root>
|
||||
<Menu.Button as={CustomButon} icon={'three-dots'} />
|
||||
<Menu.Items css={{ minWidth: '12rem' }}>
|
||||
{/* TODO remove span and render as fragment */}
|
||||
<span>
|
||||
<MenuItem
|
||||
label="Open on OpenSea"
|
||||
iconName="opensea"
|
||||
onClick={handleShareOpenSeaOnClick}
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<MenuItem
|
||||
label="Share to Twitter"
|
||||
iconName="twitter"
|
||||
onClick={handleShareOnTwitterOnClick}
|
||||
/>
|
||||
</span>
|
||||
</Menu.Items>
|
||||
</Menu.Root>
|
||||
|
||||
{/* TODO add tooltip to copy link */}
|
||||
<CustomButon icon="share" onClick={handleShareOnClick} />
|
||||
</S.Aside.Button.Container>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import { Flex, Icon, NFAIcon, ResolvedAddress } from '@/components';
|
||||
|
||||
import { IndexedNFA } from '../../indexed-nfa.context';
|
||||
import { IndexedNFAStyles as S } from '../../indexed-nfa.styles';
|
||||
|
||||
type BadgeProps = {
|
||||
verified: boolean;
|
||||
};
|
||||
|
||||
const Badge: React.FC<BadgeProps> = ({ verified }: BadgeProps) => {
|
||||
const text = useMemo(
|
||||
() => (verified ? 'Verified' : 'Unverified'),
|
||||
[verified]
|
||||
);
|
||||
|
||||
const icon = useMemo(() => (verified ? 'verified' : 'error'), [verified]);
|
||||
const color = useMemo(() => (verified ? '$green10' : '$red10'), [verified]);
|
||||
return (
|
||||
<S.Aside.Header.Badge verified={verified}>
|
||||
<Icon name={icon} css={{ color: color }} />
|
||||
{text}
|
||||
</S.Aside.Header.Badge>
|
||||
);
|
||||
};
|
||||
|
||||
export const Header: React.FC = () => {
|
||||
const { nfa } = IndexedNFA.useContext();
|
||||
|
||||
return (
|
||||
<S.Aside.Header.Wrapper>
|
||||
<S.Aside.Header.Container>
|
||||
<S.Aside.Header.Header>{nfa.name}</S.Aside.Header.Header>
|
||||
<Badge verified={nfa.verified} />
|
||||
</S.Aside.Header.Container>
|
||||
|
||||
<Flex css={{ gap: '$1h' }}>
|
||||
<NFAIcon image={nfa.logo} color={'white'} />
|
||||
<ResolvedAddress>{nfa.owner.id}</ResolvedAddress>
|
||||
</Flex>
|
||||
</S.Aside.Header.Wrapper>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { Flex, Text } from '@/components';
|
||||
import { getDate } from '@/utils';
|
||||
|
||||
import { IndexedNFA } from '../../indexed-nfa.context';
|
||||
import { IndexedNFAStyles as S } from '../../indexed-nfa.styles';
|
||||
|
||||
type HeaderDataProps = {
|
||||
label: string;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const HeaderData: React.FC<HeaderDataProps> = ({
|
||||
label,
|
||||
children,
|
||||
}: HeaderDataProps) => (
|
||||
<Flex css={{ gap: '$2', fontSize: '14px', fontWeight: '400' }}>
|
||||
<Text css={{ color: '$slate11' }}>{label}</Text>
|
||||
<Text css={{ color: '$slate12' }}>{children}</Text>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
export const NFAInfo: React.FC = () => {
|
||||
const { nfa } = IndexedNFA.useContext();
|
||||
return (
|
||||
<Flex css={{ alignItems: 'center', gap: '$2h' }}>
|
||||
<HeaderData label="Hosted NFAs">
|
||||
{nfa.accessPoints?.length ?? 0}
|
||||
</HeaderData>
|
||||
|
||||
<S.Aside.Divider.Elipse />
|
||||
|
||||
<HeaderData label="Created">{getDate(nfa.createdAt)}</HeaderData>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import { NFAPreview } from '@/components';
|
||||
import { parseNumberToHexColor } from '@/utils/color';
|
||||
|
||||
import { IndexedNFA } from '../../indexed-nfa.context';
|
||||
|
||||
export const Preview: React.FC = () => {
|
||||
const { nfa } = IndexedNFA.useContext();
|
||||
|
||||
const color = useMemo(
|
||||
() => `#${parseNumberToHexColor(nfa.color ?? '')}`,
|
||||
[nfa]
|
||||
);
|
||||
|
||||
return (
|
||||
<NFAPreview
|
||||
color={color}
|
||||
logo={nfa.logo}
|
||||
ens={nfa.ENS}
|
||||
name={nfa.name}
|
||||
size="100%"
|
||||
css={{
|
||||
borderRadius: '$lg',
|
||||
border: '1px solid $slate6',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { Flex } from '@/components';
|
||||
import { getRepositoryFromURL } from '@/utils';
|
||||
|
||||
import { IndexedNFA } from '../../indexed-nfa.context';
|
||||
import { IndexedNFAStyles as S } from '../../indexed-nfa.styles';
|
||||
import { Tab, TabContainer } from '../../tabs';
|
||||
|
||||
const OverviewFragment: React.FC = () => {
|
||||
const { nfa } = IndexedNFA.useContext();
|
||||
|
||||
return (
|
||||
<S.Aside.Overview.Container
|
||||
css={{ gap: '$4h', p: '$5 $6', height: '336px' }}
|
||||
>
|
||||
<S.Aside.Overview.Row.Container>
|
||||
<S.Aside.Overview.Row.Label>Token ID</S.Aside.Overview.Row.Label>
|
||||
<S.Aside.Overview.Row.Value>{nfa.tokenId}</S.Aside.Overview.Row.Value>
|
||||
</S.Aside.Overview.Row.Container>
|
||||
<S.Aside.Divider.Line />
|
||||
<S.Aside.Overview.Row.Container>
|
||||
<S.Aside.Overview.Row.Label>Network</S.Aside.Overview.Row.Label>
|
||||
<S.Aside.Overview.Row.Value>Mainnet</S.Aside.Overview.Row.Value>
|
||||
</S.Aside.Overview.Row.Container>
|
||||
<S.Aside.Divider.Line />
|
||||
<S.Aside.Overview.Row.Container>
|
||||
<S.Aside.Overview.Row.Label>Standard</S.Aside.Overview.Row.Label>
|
||||
<S.Aside.Overview.Row.Value>ERC_721</S.Aside.Overview.Row.Value>
|
||||
</S.Aside.Overview.Row.Container>
|
||||
<S.Aside.Divider.Line />
|
||||
<S.Aside.Overview.Row.Container>
|
||||
<S.Aside.Overview.Row.Label>Description</S.Aside.Overview.Row.Label>
|
||||
</S.Aside.Overview.Row.Container>
|
||||
<S.Aside.Overview.Description css={{ overflowY: 'scroll' }}>
|
||||
{nfa.description}
|
||||
</S.Aside.Overview.Description>
|
||||
</S.Aside.Overview.Container>
|
||||
);
|
||||
};
|
||||
|
||||
const PropertiesFragment: React.FC = () => {
|
||||
const { nfa } = IndexedNFA.useContext();
|
||||
|
||||
const traitsToShow = useMemo(() => {
|
||||
return [
|
||||
[nfa.ENS, 'ENS'],
|
||||
[getRepositoryFromURL(nfa.gitRepository.id), 'Repository'],
|
||||
[nfa.externalURL, 'Domain'],
|
||||
];
|
||||
}, [nfa]);
|
||||
|
||||
return (
|
||||
<Flex css={{ flexDirection: 'column', gap: '$3', height: '336px' }}>
|
||||
{traitsToShow.map(([value, label], index) => (
|
||||
<S.Aside.Overview.Container
|
||||
css={{ gap: '$1', p: '$2h $4' }}
|
||||
key={index}
|
||||
>
|
||||
<S.Aside.Overview.Row.Value>
|
||||
{value || '-'}
|
||||
</S.Aside.Overview.Row.Value>
|
||||
<S.Aside.Overview.Row.Label>{label}</S.Aside.Overview.Row.Label>
|
||||
</S.Aside.Overview.Container>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const TabFragment: React.FC = () => {
|
||||
const [tabSelected, setTabSelected] = useState<number>(0);
|
||||
const handleClick = (index: number): void => {
|
||||
setTabSelected(index);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TabContainer>
|
||||
{['Overview', 'Properties'].map((label, index) => (
|
||||
<Tab
|
||||
key={index}
|
||||
index={index}
|
||||
label={label}
|
||||
active={index === tabSelected}
|
||||
onTabClick={handleClick}
|
||||
/>
|
||||
))}
|
||||
</TabContainer>
|
||||
{tabSelected === 0 ? <OverviewFragment /> : <PropertiesFragment />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { App } from '@/app.context';
|
||||
import { Button } from '@/components';
|
||||
import { parseNumberToHexColor } from '@/utils/color';
|
||||
|
||||
import { IndexedNFA } from '../../indexed-nfa.context';
|
||||
import { IndexedNFAStyles as S } from '../../indexed-nfa.styles';
|
||||
import { ButtonsFragment } from './aside-buttons.fragment';
|
||||
import { Header } from './aside-header.fragment';
|
||||
import { NFAInfo } from './aside-nfa-info.fragment';
|
||||
import { Preview } from './aside-preview.fragment';
|
||||
import { TabFragment } from './aside-tabs.fragment';
|
||||
|
||||
export const IndexedNFAAsideFragment: React.FC = () => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [top, setTop] = useState<number>();
|
||||
const { nfa } = IndexedNFA.useContext();
|
||||
|
||||
const { backgroundColor } = App.useContext();
|
||||
const background = `radial-gradient(closest-corner circle at 90% 45%, #${backgroundColor}8c 1% ,#${backgroundColor}57 20%, transparent 40%), radial-gradient(closest-corner circle at 60% 25%, #${backgroundColor} 3%, #${backgroundColor}73 30%, #181818 70%)`;
|
||||
|
||||
useEffect(() => {
|
||||
setTop(ref.current?.getBoundingClientRect().top);
|
||||
}, [ref]);
|
||||
|
||||
return (
|
||||
<S.Aside.Container
|
||||
ref={ref}
|
||||
css={{ top, background, backdropFilter: 'blur(10px)' }}
|
||||
>
|
||||
<Preview />
|
||||
<Header />
|
||||
<NFAInfo />
|
||||
<ButtonsFragment />
|
||||
<Button
|
||||
as={Link}
|
||||
to={`/create-ap/${nfa.tokenId}`}
|
||||
css={{
|
||||
backgroundColor: `#${parseNumberToHexColor(nfa.color)}`,
|
||||
color: 'white',
|
||||
}}
|
||||
>{`Host ${nfa.name} NFA`}</Button>
|
||||
<TabFragment />
|
||||
</S.Aside.Container>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
export * from './aside.fragment';
|
||||
export * from './main.fragment';
|
||||
export * from './aside/aside.fragment';
|
||||
export * from './main/main.fragment';
|
||||
export * from './skeleton.fragment';
|
||||
|
|
|
|||
|
|
@ -1,121 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
|
||||
import Rectangle1 from '@/assets/Rectangle-199.png';
|
||||
import Rectangle2 from '@/assets/Rectangle-200.png';
|
||||
import Rectangle3 from '@/assets/Rectangle-201.png';
|
||||
import { Combobox, Flex, ResolvedAddress, Text } from '@/components';
|
||||
import { getTimeSince } from '@/utils';
|
||||
|
||||
import { IndexedNFA } from '../indexed-nfa.context';
|
||||
import { IndexedNFAStyles as S } from '../indexed-nfa.styles';
|
||||
|
||||
type SortItem = {
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
const orderResults: SortItem[] = [
|
||||
{ value: 'newest', label: 'Newest' },
|
||||
{ value: 'oldest', label: 'Oldest' },
|
||||
];
|
||||
|
||||
const Header: React.FC = () => {
|
||||
const [selectedValue, setSelectedValue] = useState<SortItem>(orderResults[0]);
|
||||
|
||||
const handleSortChange = (item: SortItem | undefined): void => {
|
||||
//TODO integrate with context and sort
|
||||
if (item) {
|
||||
setSelectedValue(item);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Flex css={{ justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<S.Main.Heading>Hosted NFAs</S.Main.Heading>
|
||||
<Combobox
|
||||
items={orderResults}
|
||||
selected={[selectedValue, handleSortChange]}
|
||||
css={{ minWidth: '$28' }}
|
||||
queryKey="label"
|
||||
>
|
||||
{({ Field, Options }) => (
|
||||
<>
|
||||
<Field
|
||||
css={{
|
||||
backgroundColor: 'transparent',
|
||||
borderColor: '$slate6',
|
||||
color: '$slate11',
|
||||
}}
|
||||
>
|
||||
{(selected) => selected?.label || 'Select'}
|
||||
</Field>
|
||||
<Options disableSearch css={{ minWidth: '$44', left: 'unset' }}>
|
||||
{(item) => item.label}
|
||||
</Options>
|
||||
</>
|
||||
)}
|
||||
</Combobox>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//TODO remove
|
||||
const thumbnailMocks = [Rectangle1, Rectangle2, Rectangle3];
|
||||
|
||||
const AccessPointsListFragment: React.FC = () => {
|
||||
const {
|
||||
nfa: { accessPoints },
|
||||
} = IndexedNFA.useContext();
|
||||
|
||||
return (
|
||||
<S.Main.AccessPoint.List>
|
||||
{accessPoints && accessPoints?.length > 0 ? (
|
||||
accessPoints.map((item, index) => (
|
||||
<S.Main.AccessPoint.Grid key={index}>
|
||||
<S.Main.AccessPoint.Thumbnail>
|
||||
<img
|
||||
src={
|
||||
thumbnailMocks[
|
||||
Math.floor(
|
||||
Math.random() * (Math.floor(2) - Math.ceil(0) + 1) +
|
||||
Math.ceil(0)
|
||||
)
|
||||
]
|
||||
}
|
||||
/>
|
||||
</S.Main.AccessPoint.Thumbnail>
|
||||
<S.Main.AccessPoint.Data.Container>
|
||||
<S.Main.AccessPoint.Title>{item.id}</S.Main.AccessPoint.Title>
|
||||
<Flex
|
||||
css={{ gap: '$2h', alignItems: 'center', textAlign: 'center' }}
|
||||
>
|
||||
<Text css={{ color: '$slate11' }}>
|
||||
<ResolvedAddress>{item.owner.id}</ResolvedAddress>
|
||||
</Text>
|
||||
<S.Main.Divider.Elipse />
|
||||
{/* TODO get from bunny CDN */}
|
||||
<Text css={{ color: '$slate11' }}>220 views</Text>
|
||||
<S.Main.Divider.Elipse />
|
||||
<Text css={{ color: '$slate11' }}>
|
||||
{getTimeSince(item.createdAt)}
|
||||
</Text>
|
||||
</Flex>
|
||||
</S.Main.AccessPoint.Data.Container>
|
||||
</S.Main.AccessPoint.Grid>
|
||||
))
|
||||
) : (
|
||||
<div>No access points found</div>
|
||||
)}
|
||||
</S.Main.AccessPoint.List>
|
||||
);
|
||||
};
|
||||
|
||||
export const IndexedNFAMainFragment: React.FC = () => {
|
||||
return (
|
||||
<S.Main.Container>
|
||||
<Header />
|
||||
<AccessPointsListFragment />
|
||||
</S.Main.Container>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import Rectangle1 from '@/assets/Rectangle-199.png';
|
||||
import Rectangle2 from '@/assets/Rectangle-200.png';
|
||||
import Rectangle3 from '@/assets/Rectangle-201.png';
|
||||
import { Flex, ResolvedAddress, Text } from '@/components';
|
||||
import { getTimeSince } from '@/utils';
|
||||
|
||||
import { IndexedNFA } from '../../indexed-nfa.context';
|
||||
import { IndexedNFAStyles as S } from '../../indexed-nfa.styles';
|
||||
|
||||
//TODO remove
|
||||
const thumbnailMocks = [Rectangle1, Rectangle2, Rectangle3];
|
||||
|
||||
export const AccessPointsListFragment: React.FC = () => {
|
||||
const {
|
||||
nfa: { accessPoints },
|
||||
} = IndexedNFA.useContext();
|
||||
|
||||
return (
|
||||
<S.Main.AccessPoint.List>
|
||||
{accessPoints && accessPoints?.length > 0 ? (
|
||||
accessPoints.map((item, index) => (
|
||||
<S.Main.AccessPoint.Grid key={index}>
|
||||
<S.Main.AccessPoint.Thumbnail>
|
||||
<img
|
||||
src={
|
||||
thumbnailMocks[
|
||||
Math.floor(
|
||||
Math.random() * (Math.floor(2) - Math.ceil(0) + 1) +
|
||||
Math.ceil(0)
|
||||
)
|
||||
]
|
||||
}
|
||||
/>
|
||||
</S.Main.AccessPoint.Thumbnail>
|
||||
<S.Main.AccessPoint.Data.Container>
|
||||
<S.Main.AccessPoint.Title>{item.id}</S.Main.AccessPoint.Title>
|
||||
<Flex
|
||||
css={{ gap: '$2h', alignItems: 'center', textAlign: 'center' }}
|
||||
>
|
||||
<Text css={{ color: '$slate11' }}>
|
||||
<ResolvedAddress>{item.owner.id}</ResolvedAddress>
|
||||
</Text>
|
||||
<S.Main.Divider.Elipse />
|
||||
{/* TODO get from bunny CDN */}
|
||||
<Text css={{ color: '$slate11' }}>220 views</Text>
|
||||
<S.Main.Divider.Elipse />
|
||||
<Text css={{ color: '$slate11' }}>
|
||||
{getTimeSince(item.createdAt)}
|
||||
</Text>
|
||||
</Flex>
|
||||
</S.Main.AccessPoint.Data.Container>
|
||||
</S.Main.AccessPoint.Grid>
|
||||
))
|
||||
) : (
|
||||
<S.Main.AccessPoint.NoResults>
|
||||
<h2>No hosted NFAs</h2>
|
||||
</S.Main.AccessPoint.NoResults>
|
||||
)}
|
||||
</S.Main.AccessPoint.List>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import { useState } from 'react';
|
||||
|
||||
import { Combobox, Flex } from '@/components';
|
||||
|
||||
import { IndexedNFAStyles as S } from '../../indexed-nfa.styles';
|
||||
|
||||
type SortItem = {
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
const orderResults: SortItem[] = [
|
||||
{ value: 'newest', label: 'Newest' },
|
||||
{ value: 'oldest', label: 'Oldest' },
|
||||
];
|
||||
|
||||
export const Header: React.FC = () => {
|
||||
const [selectedValue, setSelectedValue] = useState<SortItem>(orderResults[0]);
|
||||
|
||||
const handleSortChange = (item: SortItem | undefined): void => {
|
||||
//TODO integrate with context and sort
|
||||
if (item) {
|
||||
setSelectedValue(item);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Flex css={{ justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<S.Main.Heading>Hosted NFAs</S.Main.Heading>
|
||||
<Combobox
|
||||
items={orderResults}
|
||||
selected={[selectedValue, handleSortChange]}
|
||||
css={{ minWidth: '$28' }}
|
||||
queryKey="label"
|
||||
>
|
||||
{({ Field, Options }) => (
|
||||
<>
|
||||
<Field
|
||||
css={{
|
||||
backgroundColor: 'transparent',
|
||||
borderColor: '$slate6',
|
||||
color: '$slate11',
|
||||
}}
|
||||
>
|
||||
{(selected) => selected?.label || 'Select'}
|
||||
</Field>
|
||||
<Options disableSearch css={{ minWidth: '$44', left: 'unset' }}>
|
||||
{(item) => item.label}
|
||||
</Options>
|
||||
</>
|
||||
)}
|
||||
</Combobox>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { IndexedNFAStyles as S } from '../../indexed-nfa.styles';
|
||||
import { AccessPointsListFragment } from './main-ap-list.fragment';
|
||||
import { Header } from './main-header.fragment';
|
||||
|
||||
export const IndexedNFAMainFragment: React.FC = () => {
|
||||
return (
|
||||
<S.Main.Container>
|
||||
<Header />
|
||||
<AccessPointsListFragment />
|
||||
</S.Main.Container>
|
||||
);
|
||||
};
|
||||
|
|
@ -56,7 +56,6 @@ export const IndexedNFAStyles = {
|
|||
lineHeight: 1.35,
|
||||
fontWeight: 700,
|
||||
|
||||
// maxWidth: '10rem',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
|
|
@ -151,12 +150,6 @@ export const IndexedNFAStyles = {
|
|||
lineHeight: 1.35,
|
||||
fontWeight: 700,
|
||||
}),
|
||||
SectionHeading: styled('h2', {
|
||||
fontSize: '$xl',
|
||||
lineHeight: 1.2,
|
||||
fontWeight: 700,
|
||||
marginTop: Spacing,
|
||||
}),
|
||||
AccessPoint: {
|
||||
List: styled('div', {
|
||||
display: 'flex',
|
||||
|
|
@ -186,6 +179,12 @@ export const IndexedNFAStyles = {
|
|||
color: '$slate12',
|
||||
fontSize: '$lg',
|
||||
}),
|
||||
NoResults: styled('div', {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
|
||||
fontSize: '$lg',
|
||||
}),
|
||||
},
|
||||
Divider: {
|
||||
Line: styled('span', {
|
||||
|
|
@ -199,144 +198,6 @@ export const IndexedNFAStyles = {
|
|||
borderRadius: '100%',
|
||||
}),
|
||||
},
|
||||
Paragraph: styled('p', {
|
||||
color: '$slate11',
|
||||
lineHeight: 1.43,
|
||||
}),
|
||||
DataContainer: styled('div', {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
border: '1px solid $slate6',
|
||||
borderRadius: '$lg',
|
||||
padding: Spacing,
|
||||
gap: `$1`,
|
||||
}),
|
||||
DataList: styled('div', {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '$5',
|
||||
}),
|
||||
VerificationBanner: styled('div', {
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
border: '1px solid $slate6',
|
||||
borderRadius: '$lg',
|
||||
padding: '$8 $5',
|
||||
fontWeight: 700,
|
||||
overflow: 'hidden',
|
||||
|
||||
'&:after': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
right: '-$5',
|
||||
top: '-$10',
|
||||
bottom: '-$10',
|
||||
left: '84%',
|
||||
borderRadius: '80% 0 0 80%',
|
||||
},
|
||||
|
||||
variants: {
|
||||
verified: {
|
||||
true: {
|
||||
borderColor: '$green11',
|
||||
color: '$green11',
|
||||
'&:after': {
|
||||
backgroundColor: '$green11',
|
||||
},
|
||||
},
|
||||
false: {
|
||||
borderColor: '$red11',
|
||||
color: '$red11',
|
||||
'&:after': {
|
||||
backgroundColor: '$red11',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
Table: {
|
||||
Container: styled('div', {
|
||||
border: '1px solid $slate6',
|
||||
borderRadius: '10px',
|
||||
padding: '0 $5',
|
||||
|
||||
maxHeight: '15.125rem',
|
||||
overflow: 'auto',
|
||||
}),
|
||||
Root: styled('table', {
|
||||
width: 'calc(100% + 2 * $space$5)',
|
||||
margin: '0 -$5',
|
||||
}),
|
||||
Head: styled('thead', {
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
backgroundColor: '$black',
|
||||
|
||||
'&:after': {
|
||||
position: 'absolute',
|
||||
content: '""',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
borderBottom: '1px solid $slate6',
|
||||
},
|
||||
}),
|
||||
Row: styled('tr'),
|
||||
Data: styled('td', {
|
||||
padding: '$3',
|
||||
maxWidth: '10rem',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}),
|
||||
Body: styled('tbody', {
|
||||
tr: {
|
||||
'&:hover': {
|
||||
backgroundColor: '$slate6',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
},
|
||||
}),
|
||||
Marker: styled('span', {
|
||||
display: 'block',
|
||||
margin: 'auto',
|
||||
width: '0.5625rem',
|
||||
height: '0.5625rem',
|
||||
borderRadius: '$full',
|
||||
backgroundColor: '$slate6',
|
||||
|
||||
variants: {
|
||||
variant: {
|
||||
active: {
|
||||
backgroundColor: '$green11',
|
||||
},
|
||||
inactive: {
|
||||
backgroundColor: '$slate8',
|
||||
},
|
||||
},
|
||||
text: {
|
||||
true: {
|
||||
fontSize: '$xs',
|
||||
padding: '0 $2',
|
||||
width: 'fit-content',
|
||||
height: 'fit-content',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
compoundVariants: [
|
||||
{
|
||||
variant: 'active',
|
||||
text: true,
|
||||
css: {
|
||||
color: '$green11',
|
||||
backgroundColor: '$green3',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
Skeleton: styled(Skeleton, {
|
||||
|
|
|
|||
|
|
@ -54,11 +54,6 @@ export const IndexedNFAView: React.FC = () => {
|
|||
return <IndexedNFASkeletonFragment />;
|
||||
}
|
||||
|
||||
if (!data.token) {
|
||||
//TODO add 404 page
|
||||
return <div>Token not found</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<IndexedNFA.Provider nfa={data.token}>
|
||||
<S.Grid>
|
||||
|
|
|
|||
Loading…
Reference in New Issue