chore: re-organize fragments indexed nfa view

This commit is contained in:
Camila Sosa Morales 2023-05-10 15:32:46 -03:00
parent 36223a7620
commit 698238c9b9
17 changed files with 481 additions and 593 deletions

View File

@ -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>
);
};

View File

@ -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>
);
};

View File

@ -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>
);
};

View File

@ -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>
);
};

View File

@ -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',
}}
/>
);
};

View File

@ -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 />}
</>
);
};

View File

@ -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>
);
};

View File

@ -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';

View File

@ -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>
);
};

View File

@ -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>
);
};

View File

@ -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>
</>
);
};

View File

@ -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>
);
};

View File

@ -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, {

View File

@ -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>