refactor: aside component NFA detail page

This commit is contained in:
Camila Sosa Morales 2023-05-05 19:05:09 -03:00
parent 829d287b75
commit 84659d6392
13 changed files with 436 additions and 66 deletions

View File

@ -0,0 +1,18 @@
import { IconStyles as IS } from '../icon.styles';
export const Share: React.FC<IS.CustomProps> = (props) => (
<IS.Custom
{...props}
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.91684 1.66675V5.33341C3.88976 6.27575 1.64851 11.5557 0.750173 16.3334C0.716256 16.5222 5.68551 10.8682 9.91684 10.8334V14.5001L17.2502 8.08341L9.91684 1.66675Z"
stroke="white"
strokeWidth="1.41667"
strokeLinecap="round"
strokeLinejoin="round"
/>
</IS.Custom>
);

View File

@ -7,6 +7,7 @@ import { BsFillSquareFill } from '@react-icons/all-files/bs/BsFillSquareFill';
import { FaBars } from '@react-icons/all-files/fa/FaBars';
import { FaChevronRight } from '@react-icons/all-files/fa/FaChevronRight';
import { FaExternalLinkAlt } from '@react-icons/all-files/fa/FaExternalLinkAlt';
import { HiOutlineDotsHorizontal } from '@react-icons/all-files/hi/HiOutlineDotsHorizontal';
import { IoArrowBackCircleSharp } from '@react-icons/all-files/io5/IoArrowBackCircleSharp';
import { IoCheckmarkCircleSharp } from '@react-icons/all-files/io5/IoCheckmarkCircleSharp';
import { IoClose } from '@react-icons/all-files/io5/IoClose';
@ -24,6 +25,7 @@ import {
FleekName,
MetamaskIcon,
} from './custom';
import { Share } from './custom/share-icon';
export const IconLibrary = Object.freeze({
back: IoArrowBackCircleSharp,
@ -45,8 +47,10 @@ export const IconLibrary = Object.freeze({
metamask: MetamaskIcon, //remove if not used
search: BiSearch,
square: BsFillSquareFill,
share: Share,
success: AiFillCheckCircle,
twitter: AiOutlineTwitter,
'three-dots': HiOutlineDotsHorizontal,
upload: IoCloudUploadSharp,
verified: MdVerifiedUser,
});

View File

@ -6,6 +6,7 @@ export * from './spinner';
export * from './toast';
export * from './step';
export * from './nfa-card';
export * from './nfa-icon';
export * from './nfa-preview';
export * from './card-tag';
export * from './resolved-address';

View File

@ -0,0 +1,17 @@
import { NFAIconStyles as NS } from './nfa-icon.styles';
type NFAIconProps = {
image: string;
color: string;
};
export const NFAIcon: React.FC<NFAIconProps> = ({
image,
color,
}: NFAIconProps) => {
return (
<NS.Container css={{ backgroundColor: color }}>
<NS.Image src={image} />
</NS.Container>
);
};

View File

@ -9,6 +9,7 @@ import {
CardTag,
Flex,
Form,
NFAIcon,
RowData,
Spinner,
Stepper,
@ -18,9 +19,9 @@ import { getNFADocument } from '@/graphclient';
import { useAppDispatch } from '@/store';
import { bunnyCDNActions, useBunnyCDNStore } from '@/store/features/bunny-cdn';
import { AppLog } from '@/utils';
import { parseNumberToHexColor } from '@/utils/color';
import { CreateAccessPoint } from '../create-ap.context';
import { NFAIconFragment } from '../nfa-icon';
import { useAccessPointFormContext } from './create-ap.form.context';
export const SelectedNFA: React.FC = () => {
@ -28,7 +29,12 @@ export const SelectedNFA: React.FC = () => {
return (
<RowData
leftIcon={<NFAIconFragment image={nfa.logo} color={nfa.color} />}
leftIcon={
<NFAIcon
image={nfa.logo}
color={`#${parseNumberToHexColor(nfa.color)}57`}
/>
}
label={nfa.name}
rightComponent={<CardTag css={{ minWidth: '$28' }}>Selected NFA</CardTag>}
/>

View File

@ -1,21 +0,0 @@
import { parseNumberToHexColor } from '@/utils/color';
import { NFAIconStyles as NS } from './nfa-icon.styles';
type NFAIconProps = {
image: string;
color: number;
};
export const NFAIconFragment: React.FC<NFAIconProps> = ({
image,
color,
}: NFAIconProps) => {
return (
<NS.Container
css={{ backgroundColor: `#${parseNumberToHexColor(color)}57` }}
>
<NS.Image src={image} />
</NS.Container>
);
};

View File

@ -1,11 +1,24 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { Link } from 'react-router-dom';
import { Button, Flex, Icon, NFAPreview } from '@/components';
import { App } from '@/app.context';
import {
Button,
Flex,
Icon,
IconName,
NFAIcon,
NFAPreview,
ResolvedAddress,
Text,
} from '@/components';
import { forwardStyledRef } from '@/theme';
import { parseNumberToHexColor } from '@/utils/color';
import { IndexedNFA } from '../indexed-nfa.context';
import { IndexedNFAStyles as S } from '../indexed-nfa.styles';
import { Tab, TabContainer } from './tabs';
import { AppLog } from '@/utils';
const Preview: React.FC = () => {
const { nfa } = IndexedNFA.useContext();
@ -30,46 +43,221 @@ const Preview: React.FC = () => {
);
};
const CreateAccessPoint: React.FC = () => {
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>
{/* TODO remove once subrgraph integration is merged */}
<Badge verified={Math.random() > 0.5} />
</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 (
<S.Aside.CreateAccessPoint.Container>
<S.Aside.CreateAccessPoint.Heading>
Host NFA Frontend
</S.Aside.CreateAccessPoint.Heading>
{/* TODO: replace with correct text */}
<Flex css={{ alignItems: 'center', gap: '$2h' }}>
<HeaderData label="Hosted NFAs">
{nfa.accessPoints?.length ?? 0}
</HeaderData>
<S.Aside.CreateAccessPoint.Text>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce vitae
ante erat. Sed quis finibus diam.
</S.Aside.CreateAccessPoint.Text>
<S.Aside.Divider.Elipse />
<Flex css={{ gap: '$3' }}>
<Button as={Link} to={`/create-ap/${nfa.tokenId}`} colorScheme="blue">
Host NFA Frontend
</Button>
<S.Aside.CreateAccessPoint.Extra href="">
{/* TODO: place correct href */}
Learn more
<Icon name="chevron-right" />
</S.Aside.CreateAccessPoint.Extra>
</Flex>
</S.Aside.CreateAccessPoint.Container>
<HeaderData label="Created">
{/* TODO: place correct data */}
12/12/22
</HeaderData>
</Flex>
);
};
type CustomButtonProps = {
icon: IconName;
} & React.ComponentPropsWithRef<typeof Button>;
const CustomButon = forwardStyledRef<HTMLButtonElement, CustomButtonProps>(
({ icon, ...props }, ref) => (
<Button
ref={ref}
{...props}
css={{ borderRadius: '0.375rem', padding: '$2' }}
>
<Icon name={icon} />
</Button>
)
);
const ButtonsFragment: React.FC = () => {
const location = window.location.href;
const handleShareOnClick = (): void => {
navigator.clipboard.writeText(location);
AppLog.successToast('Link copied to clipboard');
};
return (
<S.Aside.Button.Container>
<CustomButon icon="three-dots" />
{/* 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'],
[nfa.gitRepository.id, 'Repository'],
[10, 'Version'],
[nfa.externalURL, 'Domain'],
];
}, [nfa]);
return (
<Flex css={{ flexDirection: 'column', gap: '$3' }}>
{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: '$6' }}>
<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>
{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 = `linear-gradient(230deg, #${backgroundColor}59 0%, #181818 80%)`;
useEffect(() => {
setTop(ref.current?.getBoundingClientRect().top);
}, [ref]);
return (
<S.Aside.Container ref={ref} css={{ top }}>
<S.Aside.Container
ref={ref}
css={{ top, background: background, backdropFilter: 'blur(10px)' }}
>
<Preview />
<CreateAccessPoint />
<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 @@
export * from './tabs';

View File

@ -0,0 +1,48 @@
import { Flex } from '@/components';
import { styled } from '@/theme';
export const TabsStyles = {
Container: styled(Flex, {
width: '100%',
}),
Tab: {
Container: styled(Flex, {
flexDirection: 'column',
flex: 1,
alignItems: 'center',
cursor: 'pointer',
variants: {
active: {
true: {
color: 'white',
},
false: {
color: '$slate8',
},
},
},
}),
Label: styled('span', {
padding: '$2h',
}),
Line: styled('span', {
width: '100%',
borderRadius: '3px',
variants: {
active: {
true: {
color: 'white',
borderBottom: '3px solid white',
},
false: {
color: '$slate8',
borderBottom: '2px solid $slate8',
mt: '0.046875rem',
},
},
},
}),
},
};

View File

@ -0,0 +1,35 @@
import { forwardStyledRef } from '@/theme';
import { TabsStyles as S } from './tabs.styles';
type TabProps = {
label: string;
index: number;
onTabClick: (index: number) => void;
} & React.ComponentPropsWithRef<typeof S.Tab.Container>;
export const Tab = forwardStyledRef<HTMLDivElement, TabProps>(
({ label, index, onTabClick, ...props }, ref) => {
const { active } = props;
const handleClick = (): void => {
onTabClick(index);
};
return (
<S.Tab.Container ref={ref} {...props} onClick={handleClick}>
<S.Tab.Label>{label}</S.Tab.Label>
<S.Tab.Line active={active} />
</S.Tab.Container>
);
}
);
type TabContainerProps = {
children: React.ReactNode;
};
export const TabContainer: React.FC<TabContainerProps> = ({
children,
}: TabContainerProps) => {
return <S.Container>{children}</S.Container>;
};

View File

@ -1,7 +1,7 @@
import { Skeleton } from '@/components';
import { Button, Flex, Skeleton, Text } from '@/components';
import { styled } from '@/theme';
const Spacing = '$5';
const Spacing = '$6';
export const IndexedNFAStyles = {
Grid: styled('div', {
@ -32,34 +32,108 @@ export const IndexedNFAStyles = {
gap: Spacing,
height: 'fit-content',
borderRadius: '$lg',
padding: Spacing,
maxWidth: '24rem',
'@media (max-width: 580px)': {
position: 'static',
},
}),
Header: {
Wrapper: styled(Flex, {
flexDirection: 'column',
gap: '$2h',
color: '$slate12',
}),
Container: styled(Flex, {
justifyContent: 'space-between',
alignItems: 'center',
}),
Header: styled('h1', {
fontSize: '2.125rem',
lineHeight: 1.35,
fontWeight: 700,
CreateAccessPoint: {
// maxWidth: '10rem',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}),
Badge: styled('span', {
height: 'fit-content',
width: 'fit-content',
fontSize: '$xs',
fontWeight: '$bold',
padding: '$0h $2',
borderRadius: '$full',
backgroundColor: '#131313',
display: 'flex',
gap: '$1h',
variants: {
verified: {
true: {
color: '$green10',
},
false: {
color: '$red10',
},
},
},
}),
},
Divider: {
Line: styled('span', {
width: '100%',
borderBottom: '1px solid $slate6',
}),
Elipse: styled('span', {
width: '0.375rem',
height: '0.375rem',
backgroundColor: '$slate8',
borderRadius: '100%',
}),
},
Button: {
Container: styled(Flex, {
gap: '$3',
fontSize: '16px',
[`${Button}`]: {
borderRadius: '0.375rem',
},
}),
},
Overview: {
Container: styled('div', {
display: 'flex',
flexDirection: 'column',
gap: Spacing,
padding: Spacing,
backgroundColor: '$blue1',
backgroundColor: '$slate4',
borderRadius: '$lg',
fontSize: '14px',
}),
Heading: styled('h2', {
fontSize: '$md',
color: '$slate12',
}),
Text: styled('p', {
fontSize: '$sm',
Row: {
Container: styled(Flex, {
justifyContent: 'space-between',
}),
Label: styled(Text, {
color: '$slate11',
}),
Value: styled(Text, {
fontWeight: '$bold',
}),
},
Description: styled('p', {
color: '$slate11',
}),
Extra: styled('a', {
},
Properties: {
Container: styled('div', {
display: 'flex',
alignItems: 'center',
color: '$slate11',
fontSize: '$sm',
gap: '$2',
flexDirection: 'column',
gap: '$1',
padding: '$2h $4',
}),
},
},
@ -90,7 +164,7 @@ export const IndexedNFAStyles = {
Elipse: styled('span', {
width: '0.375rem',
height: '0.375rem',
backgroundColor: '$slate4',
backgroundColor: '$slate11',
borderRadius: '100%',
}),
},
@ -150,7 +224,6 @@ export const IndexedNFAStyles = {
},
},
}),
Table: {
Container: styled('div', {
border: '1px solid $slate6',