diff --git a/ui/src/components/core/icon/custom/share-icon.tsx b/ui/src/components/core/icon/custom/share-icon.tsx new file mode 100644 index 0000000..eff3f7e --- /dev/null +++ b/ui/src/components/core/icon/custom/share-icon.tsx @@ -0,0 +1,18 @@ +import { IconStyles as IS } from '../icon.styles'; + +export const Share: React.FC = (props) => ( + + + +); diff --git a/ui/src/components/core/icon/icon-library.tsx b/ui/src/components/core/icon/icon-library.tsx index b350ded..b6b32ae 100644 --- a/ui/src/components/core/icon/icon-library.tsx +++ b/ui/src/components/core/icon/icon-library.tsx @@ -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, }); diff --git a/ui/src/components/index.ts b/ui/src/components/index.ts index 62f959e..351ed16 100644 --- a/ui/src/components/index.ts +++ b/ui/src/components/index.ts @@ -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'; diff --git a/ui/src/views/access-point/nfa-icon/index.ts b/ui/src/components/nfa-icon/index.ts similarity index 100% rename from ui/src/views/access-point/nfa-icon/index.ts rename to ui/src/components/nfa-icon/index.ts diff --git a/ui/src/views/access-point/nfa-icon/nfa-icon.styles.ts b/ui/src/components/nfa-icon/nfa-icon.styles.ts similarity index 100% rename from ui/src/views/access-point/nfa-icon/nfa-icon.styles.ts rename to ui/src/components/nfa-icon/nfa-icon.styles.ts diff --git a/ui/src/components/nfa-icon/nfa-icon.tsx b/ui/src/components/nfa-icon/nfa-icon.tsx new file mode 100644 index 0000000..e3ca64d --- /dev/null +++ b/ui/src/components/nfa-icon/nfa-icon.tsx @@ -0,0 +1,17 @@ +import { NFAIconStyles as NS } from './nfa-icon.styles'; + +type NFAIconProps = { + image: string; + color: string; +}; + +export const NFAIcon: React.FC = ({ + image, + color, +}: NFAIconProps) => { + return ( + + + + ); +}; diff --git a/ui/src/views/access-point/ap-form-step/create-ap-form-body.tsx b/ui/src/views/access-point/ap-form-step/create-ap-form-body.tsx index 9cdd56f..41fb535 100644 --- a/ui/src/views/access-point/ap-form-step/create-ap-form-body.tsx +++ b/ui/src/views/access-point/ap-form-step/create-ap-form-body.tsx @@ -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 ( } + leftIcon={ + + } label={nfa.name} rightComponent={Selected NFA} /> diff --git a/ui/src/views/access-point/nfa-icon/nfa-icon.tsx b/ui/src/views/access-point/nfa-icon/nfa-icon.tsx deleted file mode 100644 index 3caddb1..0000000 --- a/ui/src/views/access-point/nfa-icon/nfa-icon.tsx +++ /dev/null @@ -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 = ({ - image, - color, -}: NFAIconProps) => { - return ( - - - - ); -}; diff --git a/ui/src/views/indexed-nfa/fragments/aside.fragment.tsx b/ui/src/views/indexed-nfa/fragments/aside.fragment.tsx index f0db5d4..dd88a83 100644 --- a/ui/src/views/indexed-nfa/fragments/aside.fragment.tsx +++ b/ui/src/views/indexed-nfa/fragments/aside.fragment.tsx @@ -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 = ({ verified }: BadgeProps) => { + const text = useMemo( + () => (verified ? 'Verified' : 'Unverified'), + [verified] + ); + + const icon = useMemo(() => (verified ? 'verified' : 'error'), [verified]); + const color = useMemo(() => (verified ? '$green10' : '$red10'), [verified]); + return ( + + + {text} + + ); +}; + +const Header: React.FC = () => { + const { nfa } = IndexedNFA.useContext(); + + return ( + + + {nfa.name} + {/* TODO remove once subrgraph integration is merged */} + 0.5} /> + + + + + {nfa.owner.id} + + + ); +}; + +type HeaderDataProps = { + label: string; + children: React.ReactNode; +}; + +const HeaderData: React.FC = ({ + label, + children, +}: HeaderDataProps) => ( + + {label} + {children} + +); + +const NFAInfo: React.FC = () => { const { nfa } = IndexedNFA.useContext(); return ( - - - Host NFA Frontend - - {/* TODO: replace with correct text */} + + + {nfa.accessPoints?.length ?? 0} + - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce vitae - ante erat. Sed quis finibus diam. - + - - - - {/* TODO: place correct href */} - Learn more - - - - + + {/* TODO: place correct data */} + 12/12/22 + + + ); +}; + +type CustomButtonProps = { + icon: IconName; +} & React.ComponentPropsWithRef; + +const CustomButon = forwardStyledRef( + ({ icon, ...props }, ref) => ( + + ) +); + +const ButtonsFragment: React.FC = () => { + const location = window.location.href; + const handleShareOnClick = (): void => { + navigator.clipboard.writeText(location); + AppLog.successToast('Link copied to clipboard'); + }; + return ( + + + {/* TODO add tooltip to copy link */} + + + ); +}; + +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 ( + + {traitsToShow.map(([value, label], index) => ( + + + {value || '-'} + + {label} + + ))} + + ); +}; + +const OverviewFragment: React.FC = () => { + const { nfa } = IndexedNFA.useContext(); + + return ( + + + Token ID + {nfa.tokenId} + + + + Network + Mainnet + + + + Standard + ERC_721 + + + + Description + + + {nfa.description} + + + ); +}; + +const TabFragment: React.FC = () => { + const [tabSelected, setTabSelected] = useState(0); + const handleClick = (index: number): void => { + setTabSelected(index); + }; + return ( + <> + + {['Overview', 'Properties'].map((label, index) => ( + + ))} + + {tabSelected === 0 ? : } + ); }; export const IndexedNFAAsideFragment: React.FC = () => { const ref = useRef(null); const [top, setTop] = useState(); + 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 ( - + - +
+ + + + ); }; diff --git a/ui/src/views/indexed-nfa/fragments/tabs/index.ts b/ui/src/views/indexed-nfa/fragments/tabs/index.ts new file mode 100644 index 0000000..6dd7856 --- /dev/null +++ b/ui/src/views/indexed-nfa/fragments/tabs/index.ts @@ -0,0 +1 @@ +export * from './tabs'; diff --git a/ui/src/views/indexed-nfa/fragments/tabs/tabs.styles.ts b/ui/src/views/indexed-nfa/fragments/tabs/tabs.styles.ts new file mode 100644 index 0000000..132343e --- /dev/null +++ b/ui/src/views/indexed-nfa/fragments/tabs/tabs.styles.ts @@ -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', + }, + }, + }, + }), + }, +}; diff --git a/ui/src/views/indexed-nfa/fragments/tabs/tabs.tsx b/ui/src/views/indexed-nfa/fragments/tabs/tabs.tsx new file mode 100644 index 0000000..b83b652 --- /dev/null +++ b/ui/src/views/indexed-nfa/fragments/tabs/tabs.tsx @@ -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; + +export const Tab = forwardStyledRef( + ({ label, index, onTabClick, ...props }, ref) => { + const { active } = props; + const handleClick = (): void => { + onTabClick(index); + }; + + return ( + + {label} + + + ); + } +); + +type TabContainerProps = { + children: React.ReactNode; +}; + +export const TabContainer: React.FC = ({ + children, +}: TabContainerProps) => { + return {children}; +}; diff --git a/ui/src/views/indexed-nfa/indexed-nfa.styles.ts b/ui/src/views/indexed-nfa/indexed-nfa.styles.ts index 1e1b2c4..1217ca1 100644 --- a/ui/src/views/indexed-nfa/indexed-nfa.styles.ts +++ b/ui/src/views/indexed-nfa/indexed-nfa.styles.ts @@ -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',