From 22a6d70e986ffcf3c370e6b07f0df4fa5369eb4b Mon Sep 17 00:00:00 2001 From: Felipe Mendes Date: Thu, 20 Apr 2023 14:19:53 -0300 Subject: [PATCH 1/6] feat: UI indexed nfa view (#230) * wip: indexed nfa base layout * refactor: split aside and main components to single files * feat: indexed nfa aside styling * feat: add main section header and drescription * feat: add main section traits * feat: add verification banner in main fragment * feat: add externla link icon * feat: add table stylings and frontends section mocked * feat: add versions table mocked * feat: add small layer of responsivity * feat: add indexed nfa view skeleton * refactor: create fragments folder * refactor: split out mock and add todo comment * feat: add icon on verified banner * refactor: table stylings * feat: add chevron right icon * fix: link paths * chore: add todo comments * refactor: set initial position for aside container * refactor: improve spacings * fix: remove leftover comment --- ui/graphql/queries.graphql | 8 + ui/src/app.tsx | 9 +- ui/src/components/core/icon/icon-library.tsx | 8 +- ui/src/components/nfa-card/nfa-card.tsx | 3 +- ui/src/mocks/index.ts | 1 + ui/src/mocks/nfa.ts | 26 ++ ui/src/views/index.ts | 1 + .../indexed-nfa/fragments/aside.fragment.tsx | 75 +++++ ui/src/views/indexed-nfa/fragments/index.ts | 3 + .../indexed-nfa/fragments/main.fragment.tsx | 285 ++++++++++++++++++ .../fragments/skeleton.fragment.tsx | 16 + ui/src/views/indexed-nfa/index.ts | 1 + .../views/indexed-nfa/indexed-nfa.context.tsx | 28 ++ .../views/indexed-nfa/indexed-nfa.styles.ts | 241 +++++++++++++++ ui/src/views/indexed-nfa/indexed-nfa.tsx | 55 ++++ 15 files changed, 756 insertions(+), 4 deletions(-) create mode 100644 ui/src/mocks/nfa.ts create mode 100644 ui/src/views/indexed-nfa/fragments/aside.fragment.tsx create mode 100644 ui/src/views/indexed-nfa/fragments/index.ts create mode 100644 ui/src/views/indexed-nfa/fragments/main.fragment.tsx create mode 100644 ui/src/views/indexed-nfa/fragments/skeleton.fragment.tsx create mode 100644 ui/src/views/indexed-nfa/index.ts create mode 100644 ui/src/views/indexed-nfa/indexed-nfa.context.tsx create mode 100644 ui/src/views/indexed-nfa/indexed-nfa.styles.ts create mode 100644 ui/src/views/indexed-nfa/indexed-nfa.tsx diff --git a/ui/graphql/queries.graphql b/ui/graphql/queries.graphql index 3e1f0c5..a7cac93 100644 --- a/ui/graphql/queries.graphql +++ b/ui/graphql/queries.graphql @@ -41,7 +41,15 @@ query getLatestNFAs { query getNFA($id: ID!) { token(id: $id) { tokenId + owner { + id + } name + description + ENS + externalURL + logo + color } } diff --git a/ui/src/app.tsx b/ui/src/app.tsx index 16fc6fb..1a068d4 100644 --- a/ui/src/app.tsx +++ b/ui/src/app.tsx @@ -3,7 +3,13 @@ import { HashRouter, Navigate, Route, Routes } from 'react-router-dom'; import { themeGlobals } from '@/theme/globals'; import { AppPage, ToastProvider } from './components'; -import { ComponentsTest, CreateAP, Explore, Home, Mint } from './views'; +import { + ComponentsTest, + CreateAP, + Explore, + IndexedNFAView, + Mint, +} from './views'; export const App: React.FC = () => { themeGlobals(); @@ -17,6 +23,7 @@ export const App: React.FC = () => { } /> } /> } /> + } /> {/** TODO remove for release */} } /> } /> diff --git a/ui/src/components/core/icon/icon-library.tsx b/ui/src/components/core/icon/icon-library.tsx index a8c481b..70d5473 100644 --- a/ui/src/components/core/icon/icon-library.tsx +++ b/ui/src/components/core/icon/icon-library.tsx @@ -4,12 +4,15 @@ import { AiOutlineTwitter } from '@react-icons/all-files/ai/AiOutlineTwitter'; import { BiGitBranch } from '@react-icons/all-files/bi/BiGitBranch'; import { BiSearch } from '@react-icons/all-files/bi/BiSearch'; import { BsFillSquareFill } from '@react-icons/all-files/bs/BsFillSquareFill'; +import { FaChevronRight } from '@react-icons/all-files/fa/FaChevronRight'; +import { FaExternalLinkAlt } from '@react-icons/all-files/fa/FaExternalLinkAlt'; 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'; import { IoCloudUploadSharp } from '@react-icons/all-files/io5/IoCloudUploadSharp'; import { IoInformationCircleSharp } from '@react-icons/all-files/io5/IoInformationCircleSharp'; import { IoLogoGithub } from '@react-icons/all-files/io5/IoLogoGithub'; +import { MdVerifiedUser } from '@react-icons/all-files/md/MdVerifiedUser'; import { BetaTag, @@ -28,19 +31,22 @@ export const IconLibrary = Object.freeze({ check: AiOutlineCheck, 'check-circle': IoCheckmarkCircleSharp, 'chevron-down': ChevronDownIcon, + 'chevron-right': FaChevronRight, close: IoClose, error: ErrorIcon, ethereum: EthereumIcon, + 'external-link': FaExternalLinkAlt, fleekLogo: FleekLogo, fleekName: FleekName, github: IoLogoGithub, info: IoInformationCircleSharp, - upload: IoCloudUploadSharp, metamask: MetamaskIcon, //remove if not used search: BiSearch, square: BsFillSquareFill, success: AiFillCheckCircle, twitter: AiOutlineTwitter, + upload: IoCloudUploadSharp, + verified: MdVerifiedUser, }); export type IconName = keyof typeof IconLibrary; diff --git a/ui/src/components/nfa-card/nfa-card.tsx b/ui/src/components/nfa-card/nfa-card.tsx index 6cd545e..1f09fbd 100644 --- a/ui/src/components/nfa-card/nfa-card.tsx +++ b/ui/src/components/nfa-card/nfa-card.tsx @@ -32,8 +32,7 @@ export type NFACardProps = Omit< export const NFACard: React.FC = forwardStyledRef< HTMLAnchorElement, NFACardProps - // TODO: Set default path to NFA page ->(({ data, to = `/create-ap/${data.tokenId}`, ...props }, ref) => { +>(({ data, to = `/nfa/${data.tokenId}`, ...props }, ref) => { const { name, color, ENS, logo, accessPoints } = data; const apCounter = useMemo(() => accessPoints?.length ?? 0, [accessPoints]); diff --git a/ui/src/mocks/index.ts b/ui/src/mocks/index.ts index 3668f82..8941f0a 100644 --- a/ui/src/mocks/index.ts +++ b/ui/src/mocks/index.ts @@ -1,3 +1,4 @@ export * from './mint-site'; export * from './detail'; export * from './list'; +export * from './nfa'; diff --git a/ui/src/mocks/nfa.ts b/ui/src/mocks/nfa.ts new file mode 100644 index 0000000..85f21a6 --- /dev/null +++ b/ui/src/mocks/nfa.ts @@ -0,0 +1,26 @@ +export const NFAMock = { + id: '6', + tokenId: '6', + name: 'Polygon', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sit amet velit dolor. Praesent dapibus euismod molestie. Duis maximus porttitor odio. Duis quis lorem id lacus cursus commodo vel vehicula mauris.', + externalURL: 'https://polygon.com', + ENS: 'polygon.eth', + logo: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI0LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAzOC40IDMzLjUiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDM4LjQgMzMuNTsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgoJLnN0MHtmaWxsOiM4MjQ3RTU7fQo8L3N0eWxlPgo8Zz4KCTxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0yOSwxMC4yYy0wLjctMC40LTEuNi0wLjQtMi40LDBMMjEsMTMuNWwtMy44LDIuMWwtNS41LDMuM2MtMC43LDAuNC0xLjYsMC40LTIuNCwwTDUsMTYuMwoJCWMtMC43LTAuNC0xLjItMS4yLTEuMi0yLjF2LTVjMC0wLjgsMC40LTEuNiwxLjItMi4xbDQuMy0yLjVjMC43LTAuNCwxLjYtMC40LDIuNCwwTDE2LDcuMmMwLjcsMC40LDEuMiwxLjIsMS4yLDIuMXYzLjNsMy44LTIuMlY3CgkJYzAtMC44LTAuNC0xLjYtMS4yLTIuMWwtOC00LjdjLTAuNy0wLjQtMS42LTAuNC0yLjQsMEwxLjIsNUMwLjQsNS40LDAsNi4yLDAsN3Y5LjRjMCwwLjgsMC40LDEuNiwxLjIsMi4xbDguMSw0LjcKCQljMC43LDAuNCwxLjYsMC40LDIuNCwwbDUuNS0zLjJsMy44LTIuMmw1LjUtMy4yYzAuNy0wLjQsMS42LTAuNCwyLjQsMGw0LjMsMi41YzAuNywwLjQsMS4yLDEuMiwxLjIsMi4xdjVjMCwwLjgtMC40LDEuNi0xLjIsMi4xCgkJTDI5LDI4LjhjLTAuNywwLjQtMS42LDAuNC0yLjQsMGwtNC4zLTIuNWMtMC43LTAuNC0xLjItMS4yLTEuMi0yLjFWMjFsLTMuOCwyLjJ2My4zYzAsMC44LDAuNCwxLjYsMS4yLDIuMWw4LjEsNC43CgkJYzAuNywwLjQsMS42LDAuNCwyLjQsMGw4LjEtNC43YzAuNy0wLjQsMS4yLTEuMiwxLjItMi4xVjE3YzAtMC44LTAuNC0xLjYtMS4yLTIuMUwyOSwxMC4yeiIvPgo8L2c+Cjwvc3ZnPgo=', + color: 8668388, + accessPointAutoApproval: true, + owner: { + id: '0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049', + collection: true, + }, + mintedBy: '0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049', + controllers: [], + gitRepository: { + id: '', + }, + commitHash: '0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049', + accessPoints: [], + verifier: { + id: '0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049', + }, +}; diff --git a/ui/src/views/index.ts b/ui/src/views/index.ts index 129b412..acf11b6 100644 --- a/ui/src/views/index.ts +++ b/ui/src/views/index.ts @@ -3,3 +3,4 @@ export * from './mint'; export * from './components-test'; export * from './explore'; export * from './access-point'; +export * from './indexed-nfa'; diff --git a/ui/src/views/indexed-nfa/fragments/aside.fragment.tsx b/ui/src/views/indexed-nfa/fragments/aside.fragment.tsx new file mode 100644 index 0000000..69f07c0 --- /dev/null +++ b/ui/src/views/indexed-nfa/fragments/aside.fragment.tsx @@ -0,0 +1,75 @@ +import { useEffect, useMemo, useRef, useState } from 'react'; +import { Link } from 'react-router-dom'; + +import { Button, Flex, Icon, NFAPreview } from '@/components'; + +import { IndexedNFA } from '../indexed-nfa.context'; +import { IndexedNFAStyles as S } from '../indexed-nfa.styles'; + +const Preview: React.FC = () => { + const { nfa } = IndexedNFA.useContext(); + + const color = useMemo( + // TODO: replace with util function + () => `#${`000000${nfa.color.toString(16)}`.slice(-6)}`, + [nfa] + ); + + return ( + + ); +}; + +const CreateAccessPoint: React.FC = () => { + const { nfa } = IndexedNFA.useContext(); + return ( + + + Host NFA Frontend + + {/* TODO: replace with correct text */} + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce vitae + ante erat. Sed quis finibus diam. + + + + + + {/* TODO: place correct href */} + Learn more + + + + + ); +}; + +export const IndexedNFAAsideFragment: React.FC = () => { + const ref = useRef(null); + const [top, setTop] = useState(); + + useEffect(() => { + setTop(ref.current?.getBoundingClientRect().top); + }, [ref]); + + return ( + + + + + ); +}; diff --git a/ui/src/views/indexed-nfa/fragments/index.ts b/ui/src/views/indexed-nfa/fragments/index.ts new file mode 100644 index 0000000..d90e1fb --- /dev/null +++ b/ui/src/views/indexed-nfa/fragments/index.ts @@ -0,0 +1,3 @@ +export * from './aside.fragment'; +export * from './main.fragment'; +export * from './skeleton.fragment'; diff --git a/ui/src/views/indexed-nfa/fragments/main.fragment.tsx b/ui/src/views/indexed-nfa/fragments/main.fragment.tsx new file mode 100644 index 0000000..286273e --- /dev/null +++ b/ui/src/views/indexed-nfa/fragments/main.fragment.tsx @@ -0,0 +1,285 @@ +import React, { useMemo } from 'react'; + +import { Flex, Icon, IconName, ResolvedAddress, Text } from '@/components'; + +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 = ({ + label, + children, +}: HeaderDataProps) => ( + + {label} + {children} + +); + +const Header: React.FC = () => { + const { nfa } = IndexedNFA.useContext(); + + return ( + <> + {nfa.name} + + + {nfa.owner.id} + + + + + + {/* TODO: place correct data */} + 12/12/22 + + + + + + {nfa.accessPoints?.length ?? 0} + + + + + ); +}; + +const Description: React.FC = () => { + const { nfa } = IndexedNFA.useContext(); + + return ( + <> + + Description + + + {nfa.description} + + + ); +}; + +type DataWrapperProps = React.PropsWithChildren<{ + label: string | number; +}>; + +const DataWrapper: React.FC = ({ + children, + label, +}: DataWrapperProps) => ( + + {children || '-'} + {label} + +); + +const Traits: React.FC = () => { + const { nfa } = IndexedNFA.useContext(); + + // TODO: place correct data + const traitsToShow = useMemo(() => { + return [ + [nfa.ENS, 'ENS'], + [nfa.gitRepository.id, 'Repository'], + [10, 'Version'], + [nfa.externalURL, 'Domain'], + [nfa.externalURL, 'Domain 2'], + ]; + }, [nfa]); + + return ( + <> + Traits + + {traitsToShow.map(([value, label]) => ( + + {value} + + ))} + + + ); +}; + +type VerificationBannerProps = { + verified: boolean; +}; + +const VerificationBanner: React.FC = ({ + verified, +}: VerificationBannerProps) => { + const [text, icon] = useMemo<[string, IconName]>(() => { + if (verified) + return ['This Non Fungible Application is Verified.', 'verified']; + return ['This Non Fungible Application is not Verified.', 'error']; + }, [verified]); + + return ( + + {text} + + + ); +}; + +const Verification: React.FC = () => { + return ( + <> + Verification + {/* TODO: Get verified from context */} + 0.5} /> + + {/* TODO: place correct data */} + polygon.eth + polygon/fe + + + ); +}; + +// TODO: replace mocks with fetched data +const apMocks = new Array(10).fill(0).map((_, index) => ({ + approved: Math.random() > 0.5, + domain: `domain${index}.com`, + owner: '0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049', + createdAt: `${Math.floor(Math.random() * 30)}m ago`, +})); + +const AccessPoints: React.FC = () => { + return ( + <> + Frontends + + + + + + + + + + + + + + + Domain + Owner + Created + + + + + {apMocks.map((item) => ( + + + + + {item.domain} + + {item.owner} + + {item.createdAt} + + + + + ))} + + + + + ); +}; + +// TODO: replace mocks with fetched data +const versionsMock = new Array(10).fill(0).map((_, index) => ({ + live: index === 0, + commit: (Math.random() * 0xfffffffff).toString(16), + preview: `test: subgraph matchstick tests for access points and acl refactor (#150 + ) + + * fix: errors from deprecated entities.`, + time: `${Math.floor(Math.random() * 30)}m ago`, +})); + +const Versions: React.FC = () => { + return ( + <> + Versions + + + + + + + + + + + + + + + Commit + Preview + Time + + + + + {versionsMock.map((item) => ( + + + + {item.live && 'Live'} + + + {item.commit.slice(0, 6)} + + {item.preview} + + {item.time} + + + + + ))} + + + + + ); +}; + +export const IndexedNFAMainFragment: React.FC = () => { + return ( + +
+ + + + + + + ); +}; diff --git a/ui/src/views/indexed-nfa/fragments/skeleton.fragment.tsx b/ui/src/views/indexed-nfa/fragments/skeleton.fragment.tsx new file mode 100644 index 0000000..e1e05a4 --- /dev/null +++ b/ui/src/views/indexed-nfa/fragments/skeleton.fragment.tsx @@ -0,0 +1,16 @@ +import { IndexedNFAStyles as S } from '../indexed-nfa.styles'; + +export const IndexedNFASkeletonFragment: React.FC = () => ( + + + + + + + + + + + + +); diff --git a/ui/src/views/indexed-nfa/index.ts b/ui/src/views/indexed-nfa/index.ts new file mode 100644 index 0000000..c044971 --- /dev/null +++ b/ui/src/views/indexed-nfa/index.ts @@ -0,0 +1 @@ +export * from './indexed-nfa'; diff --git a/ui/src/views/indexed-nfa/indexed-nfa.context.tsx b/ui/src/views/indexed-nfa/indexed-nfa.context.tsx new file mode 100644 index 0000000..fc99dac --- /dev/null +++ b/ui/src/views/indexed-nfa/indexed-nfa.context.tsx @@ -0,0 +1,28 @@ +import { Owner, Token } from '@/graphclient'; +import { createContext } from '@/utils'; + +const [Provider, useContext] = createContext({ + name: 'IndexedNFA.Context', + hookName: 'IndexedNFA.useContext', + providerName: 'IndexedNFA.Provider', +}); + +export const IndexedNFA = { + useContext, + Provider: ({ children, nfa }: IndexedNFA.ProviderProps): JSX.Element => { + return {children}; + }, +}; + +export namespace IndexedNFA { + export type Context = { + nfa: Omit & { + owner: Pick; + }; + }; + + export type ProviderProps = { + children: React.ReactNode | React.ReactNode[]; + nfa: Context['nfa']; + }; +} diff --git a/ui/src/views/indexed-nfa/indexed-nfa.styles.ts b/ui/src/views/indexed-nfa/indexed-nfa.styles.ts new file mode 100644 index 0000000..1e1b2c4 --- /dev/null +++ b/ui/src/views/indexed-nfa/indexed-nfa.styles.ts @@ -0,0 +1,241 @@ +import { Skeleton } from '@/components'; +import { styled } from '@/theme'; + +const Spacing = '$5'; + +export const IndexedNFAStyles = { + Grid: styled('div', { + display: 'grid', + gridTemplateAreas: '"aside main"', + gridTemplateColumns: '24.0625rem 1fr', + gridTemplateRows: 'fit-content', + gap: `calc(2 * ${Spacing})`, + padding: Spacing, + + '@media (max-width: 1080px)': { + gridTemplateColumns: '20rem 1fr', + }, + + '@media (max-width: 580px)': { + gridTemplateAreas: '"aside" "main"', + gridTemplateColumns: '1fr', + }, + }), + + Aside: { + Container: styled('aside', { + gridArea: 'aside', + position: 'sticky', + + display: 'flex', + flexDirection: 'column', + gap: Spacing, + height: 'fit-content', + + '@media (max-width: 580px)': { + position: 'static', + }, + }), + + CreateAccessPoint: { + Container: styled('div', { + display: 'flex', + flexDirection: 'column', + gap: Spacing, + padding: Spacing, + backgroundColor: '$blue1', + borderRadius: '$lg', + }), + Heading: styled('h2', { + fontSize: '$md', + color: '$slate12', + }), + Text: styled('p', { + fontSize: '$sm', + color: '$slate11', + }), + Extra: styled('a', { + display: 'flex', + alignItems: 'center', + color: '$slate11', + fontSize: '$sm', + gap: '$2', + }), + }, + }, + + Main: { + Container: styled('main', { + gridArea: 'main', + display: 'flex', + flexDirection: 'column', + gap: Spacing, + }), + Heading: styled('h1', { + fontSize: '2.125rem', + lineHeight: 1.35, + fontWeight: 700, + }), + SectionHeading: styled('h2', { + fontSize: '$xl', + lineHeight: 1.2, + fontWeight: 700, + marginTop: Spacing, + }), + Divider: { + Line: styled('span', { + width: '100%', + borderBottom: '1px solid $slate6', + }), + Elipse: styled('span', { + width: '0.375rem', + height: '0.375rem', + backgroundColor: '$slate4', + 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, { + borderRadius: '$lg', + }), +}; diff --git a/ui/src/views/indexed-nfa/indexed-nfa.tsx b/ui/src/views/indexed-nfa/indexed-nfa.tsx new file mode 100644 index 0000000..88cb844 --- /dev/null +++ b/ui/src/views/indexed-nfa/indexed-nfa.tsx @@ -0,0 +1,55 @@ +import { useQuery } from '@apollo/client'; +import { ethers } from 'ethers'; +import { useNavigate, useParams } from 'react-router-dom'; + +import { getNFADocument } from '@/graphclient'; +import { NFAMock } from '@/mocks'; +import { AppLog } from '@/utils'; + +import { + IndexedNFAAsideFragment, + IndexedNFAMainFragment, + IndexedNFASkeletonFragment, +} from './fragments'; +import { IndexedNFA } from './indexed-nfa.context'; +import { IndexedNFAStyles as S } from './indexed-nfa.styles'; + +export const IndexedNFAView: React.FC = () => { + const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); + + const handleError = (error: unknown): void => { + AppLog.errorToast( + `It was not possible to find the NFA with id "${id}"`, + error + ); + navigate('/', { replace: true }); + }; + + const { loading, data = { token: {} } } = useQuery(getNFADocument, { + skip: id === undefined, + variables: { + id: ethers.utils.hexlify(Number(id)), + }, + onCompleted(data) { + if (!data.token) handleError(new Error('Token not found')); + }, + onError(error) { + handleError(error); + }, + }); + + if (loading) { + return ; + } + + // TODO: replace NFAMock with real data from useQuery + return ( + + + + + + + ); +}; From 21b96601646fb1060cb90ceb95a4f47f6f700537 Mon Sep 17 00:00:00 2001 From: Camila Sosa Morales Date: Fri, 21 Apr 2023 15:57:20 -0300 Subject: [PATCH 2/6] feat: UI ap flow (#223) * chore: AP flow based on designs * chore: preview ap creation * style: max-width for explore container * fix: fix setArgs * feat: add success step for access point creation * feat: add dots spinner * chore: AP flow finished * chore: release changes * feat: store ens once the user log in with wallet * feat: add mocked function for bunny cdn creation * chore: finish bunny cdn mocked functions * chore: run eslint * feat: add domain validator * style: move animation to stitches keyframes * chore: set ens only when is not set * Update ui/src/store/features/bunny-cdn/async-thunk/verify-ap.ts Co-authored-by: Felipe Mendes * chore: remove nfa-picker * chore: get nfa name * chore: change verify thunk name * fix: fix verifyPullzone thunk --------- Co-authored-by: Felipe Mendes --- ui/src/app.tsx | 1 - ui/src/components/card-tag/card-tag.ts | 10 ++ ui/src/components/card-tag/index.ts | 1 + .../core/input/input-file.styles.ts | 1 - ui/src/components/index.ts | 1 + .../layout/nav-bar/connect-wallet-button.tsx | 18 ++ .../resolved-address/resolved-address.tsx | 7 +- ui/src/components/spinner/index.ts | 1 + ui/src/components/spinner/spinner-dot.tsx | 21 +++ ui/src/components/spinner/spinner.styles.ts | 55 +++++- ui/src/mocks/bunny-cdn.ts | 21 +++ ui/src/mocks/index.ts | 1 + .../bunny-cdn/async-thunk/create-cdn.ts | 35 ++++ .../features/bunny-cdn/async-thunk/index.ts | 2 + .../bunny-cdn/async-thunk/verify-pullzone.ts | 31 ++++ .../features/bunny-cdn/bunny-cdn-slice.ts | 51 ++++++ ui/src/store/features/bunny-cdn/index.ts | 1 + ui/src/store/features/index.ts | 1 + ui/src/store/store.ts | 4 +- ui/src/utils/color.ts | 15 ++ ui/src/utils/string-validators.ts | 11 ++ .../ap-form-step/create-ap-form-body.tsx | 156 ++++++++++++++++++ .../{ => ap-form-step}/create-ap-form.tsx | 13 +- .../create-ap.form.context.ts | 6 +- .../views/access-point/ap-form-step/index.ts | 2 + .../ap-record-step/ap-record-body.tsx | 80 +++++++++ .../ap-record-step/ap-record-header.tsx | 29 ++++ .../ap-record-step/ap-record-step.tsx | 13 ++ .../access-point/ap-record-step/index.ts | 1 + .../ap-record-step/record-step.utils.ts | 5 + .../views/access-point/create-ap-preview.tsx | 79 ++++++--- .../views/access-point/create-ap-success.tsx | 47 ++++++ .../views/access-point/create-ap.context.tsx | 31 ++-- .../access-point/create-ap.form-body.tsx | 100 ----------- .../views/access-point/create-ap.stepper.tsx | 31 +++- ui/src/views/access-point/create-ap.tsx | 4 +- .../display-text/display-text.styles.ts | 25 +++ .../display-text/display-text.tsx | 17 ++ .../views/access-point/display-text/index.ts | 1 + ui/src/views/access-point/nfa-icon/index.ts | 1 + .../access-point/nfa-icon/nfa-icon.styles.ts | 15 ++ .../views/access-point/nfa-icon/nfa-icon.tsx | 21 +++ ui/src/views/access-point/nfa-picker.tsx | 36 ---- .../views/components-test/components-test.tsx | 4 +- ui/src/views/components-test/spinner-test.tsx | 18 ++ ui/src/views/explore/explore.styles.ts | 2 +- .../repo-configuration-body.tsx | 15 +- .../repository.tsx | 1 + .../mint/github-step/steps/repository-row.tsx | 1 - ui/src/views/mint/mint-stepper.tsx | 62 ++++--- .../mint/nfa-step/form-step/form.utils.ts | 8 - .../mint/nfa-step/form-step/mint-form.tsx | 6 +- 52 files changed, 868 insertions(+), 251 deletions(-) create mode 100644 ui/src/components/card-tag/card-tag.ts create mode 100644 ui/src/components/card-tag/index.ts create mode 100644 ui/src/components/spinner/spinner-dot.tsx create mode 100644 ui/src/mocks/bunny-cdn.ts create mode 100644 ui/src/store/features/bunny-cdn/async-thunk/create-cdn.ts create mode 100644 ui/src/store/features/bunny-cdn/async-thunk/index.ts create mode 100644 ui/src/store/features/bunny-cdn/async-thunk/verify-pullzone.ts create mode 100644 ui/src/store/features/bunny-cdn/bunny-cdn-slice.ts create mode 100644 ui/src/store/features/bunny-cdn/index.ts create mode 100644 ui/src/utils/color.ts create mode 100644 ui/src/views/access-point/ap-form-step/create-ap-form-body.tsx rename ui/src/views/access-point/{ => ap-form-step}/create-ap-form.tsx (73%) rename ui/src/views/access-point/{ => ap-form-step}/create-ap.form.context.ts (83%) create mode 100644 ui/src/views/access-point/ap-form-step/index.ts create mode 100644 ui/src/views/access-point/ap-record-step/ap-record-body.tsx create mode 100644 ui/src/views/access-point/ap-record-step/ap-record-header.tsx create mode 100644 ui/src/views/access-point/ap-record-step/ap-record-step.tsx create mode 100644 ui/src/views/access-point/ap-record-step/index.ts create mode 100644 ui/src/views/access-point/ap-record-step/record-step.utils.ts create mode 100644 ui/src/views/access-point/create-ap-success.tsx delete mode 100644 ui/src/views/access-point/create-ap.form-body.tsx create mode 100644 ui/src/views/access-point/display-text/display-text.styles.ts create mode 100644 ui/src/views/access-point/display-text/display-text.tsx create mode 100644 ui/src/views/access-point/display-text/index.ts create mode 100644 ui/src/views/access-point/nfa-icon/index.ts create mode 100644 ui/src/views/access-point/nfa-icon/nfa-icon.styles.ts create mode 100644 ui/src/views/access-point/nfa-icon/nfa-icon.tsx delete mode 100644 ui/src/views/access-point/nfa-picker.tsx create mode 100644 ui/src/views/components-test/spinner-test.tsx diff --git a/ui/src/app.tsx b/ui/src/app.tsx index 1a068d4..8035d7e 100644 --- a/ui/src/app.tsx +++ b/ui/src/app.tsx @@ -21,7 +21,6 @@ export const App: React.FC = () => { } /> } /> - } /> } /> } /> {/** TODO remove for release */} diff --git a/ui/src/components/card-tag/card-tag.ts b/ui/src/components/card-tag/card-tag.ts new file mode 100644 index 0000000..62e340c --- /dev/null +++ b/ui/src/components/card-tag/card-tag.ts @@ -0,0 +1,10 @@ +import { styled } from '@/theme'; + +export const CardTag = styled('span', { + fontSize: '$sm', + backgroundColor: '$slate4', + color: '$slate11', + p: '$1 $3h', + height: '$7', + borderRadius: '$md', +}); diff --git a/ui/src/components/card-tag/index.ts b/ui/src/components/card-tag/index.ts new file mode 100644 index 0000000..602b85e --- /dev/null +++ b/ui/src/components/card-tag/index.ts @@ -0,0 +1 @@ +export * from './card-tag'; diff --git a/ui/src/components/core/input/input-file.styles.ts b/ui/src/components/core/input/input-file.styles.ts index d246812..7427094 100644 --- a/ui/src/components/core/input/input-file.styles.ts +++ b/ui/src/components/core/input/input-file.styles.ts @@ -24,6 +24,5 @@ export abstract class InputFileStyles { '&[aria-invalid=true], &[data-invalid]': { borderColor: '$red9', }, - //TODO add error state }); } diff --git a/ui/src/components/index.ts b/ui/src/components/index.ts index 8e58b16..f877380 100644 --- a/ui/src/components/index.ts +++ b/ui/src/components/index.ts @@ -7,4 +7,5 @@ export * from './toast'; export * from './step'; export * from './nfa-card'; export * from './nfa-preview'; +export * from './card-tag'; export * from './resolved-address'; diff --git a/ui/src/components/layout/nav-bar/connect-wallet-button.tsx b/ui/src/components/layout/nav-bar/connect-wallet-button.tsx index 9f5c29c..0002553 100644 --- a/ui/src/components/layout/nav-bar/connect-wallet-button.tsx +++ b/ui/src/components/layout/nav-bar/connect-wallet-button.tsx @@ -1,11 +1,29 @@ import { Avatar, ConnectKitButton } from 'connectkit'; import { Button, Flex } from '@/components'; +import { ENSActions, useAppDispatch, useENSStore } from '@/store'; export const ConnectWalletButton: React.FC = () => { + const { addressMap } = useENSStore(); + const dispatch = useAppDispatch(); + + const setEnsNameStore = (ensName: string, address: string): void => { + const stored = addressMap[address] || {}; + if (typeof stored.state !== 'undefined') return; + + dispatch( + ENSActions.setAddress({ + key: address, + value: { state: 'success', value: ensName }, + }) + ); + }; + return ( {({ isConnected, show, truncatedAddress, address, ensName }) => { + if (ensName && address) setEnsNameStore(ensName, address); + return ( + + ); +}; diff --git a/ui/src/views/access-point/create-ap-form.tsx b/ui/src/views/access-point/ap-form-step/create-ap-form.tsx similarity index 73% rename from ui/src/views/access-point/create-ap-form.tsx rename to ui/src/views/access-point/ap-form-step/create-ap-form.tsx index 643f38c..0ff5e55 100644 --- a/ui/src/views/access-point/create-ap-form.tsx +++ b/ui/src/views/access-point/ap-form-step/create-ap-form.tsx @@ -1,6 +1,6 @@ -import { Card, Grid, Icon, IconButton, Stepper } from '@/components'; +import { Card, Flex, Icon, IconButton, Stepper } from '@/components'; -import { CreateAccessPointFormBody } from './create-ap.form-body'; +import { CreateAccessPointFormBody } from './create-ap-form-body'; export const CreateAccessPointForm: React.FC = () => { const { prevStep } = Stepper.useContext(); @@ -8,7 +8,7 @@ export const CreateAccessPointForm: React.FC = () => { return ( { } /> - - + ); diff --git a/ui/src/views/access-point/create-ap.form.context.ts b/ui/src/views/access-point/ap-form-step/create-ap.form.context.ts similarity index 83% rename from ui/src/views/access-point/create-ap.form.context.ts rename to ui/src/views/access-point/ap-form-step/create-ap.form.context.ts index fea18bf..60228a0 100644 --- a/ui/src/views/access-point/create-ap.form.context.ts +++ b/ui/src/views/access-point/ap-form-step/create-ap.form.context.ts @@ -5,7 +5,7 @@ import { createContext, StringValidators } from '@/utils'; export type CreateAccessPointFormContext = { form: { - appName: FormField; + domain: FormField; isValid: ReactState; }; }; @@ -20,9 +20,9 @@ export const [CreateAccessPointFormProvider, useAccessPointFormContext] = export const useAccessPointFormContextInit = (): CreateAccessPointFormContext => ({ form: { - appName: useFormField('appName', [ + domain: useFormField('domain', [ StringValidators.required, - StringValidators.maxLength(50), + StringValidators.isValidDomain, ]), isValid: useState(false), }, diff --git a/ui/src/views/access-point/ap-form-step/index.ts b/ui/src/views/access-point/ap-form-step/index.ts new file mode 100644 index 0000000..8b94558 --- /dev/null +++ b/ui/src/views/access-point/ap-form-step/index.ts @@ -0,0 +1,2 @@ +export * from './create-ap.form.context'; +export * from './create-ap-form'; diff --git a/ui/src/views/access-point/ap-record-step/ap-record-body.tsx b/ui/src/views/access-point/ap-record-step/ap-record-body.tsx new file mode 100644 index 0000000..e14bd96 --- /dev/null +++ b/ui/src/views/access-point/ap-record-step/ap-record-body.tsx @@ -0,0 +1,80 @@ +import { useEffect, useMemo } from 'react'; + +import { Button, Card, Grid, SpinnerDot, Stepper, Text } from '@/components'; +import { bunnyCDNActions, useAppDispatch, useBunnyCDNStore } from '@/store'; + +import { useAccessPointFormContext } from '../ap-form-step'; +import { CreateAccessPoint } from '../create-ap.context'; +import { DisplayText } from '../display-text'; +import { isSubdomain } from './record-step.utils'; + +export const APRecordCardBody: React.FC = () => { + const dispatch = useAppDispatch(); + const { bunnyURL, state } = useBunnyCDNStore(); + const { + nfa: { domain: nfaDomain }, + } = CreateAccessPoint.useContext(); + const { + form: { + domain: { + value: [accesPointDomain], + }, + }, + } = useAccessPointFormContext(); + const { nextStep } = Stepper.useContext(); + + const isSudomain = useMemo( + () => isSubdomain(accesPointDomain), + [accesPointDomain] + ); + + useEffect(() => { + if (state === 'success') { + dispatch(bunnyCDNActions.setState(undefined)); + nextStep(); + } + }, [state, nextStep, dispatch]); + + const handleContinueClick = (): void => { + dispatch(bunnyCDNActions.verifyBunnyPullzone(nfaDomain)); + }; + + return ( + + {state === 'loading' ? ( + + + + Waiting for DNS propagation, allow a few minutes. + + + ) : ( + + + {`Create a ${ + isSudomain ? 'CNAME' : 'ANAME' + } record in your DNS provider pointing to our CDN + endpoint.`} + + + + + + + )} + + ); +}; diff --git a/ui/src/views/access-point/ap-record-step/ap-record-header.tsx b/ui/src/views/access-point/ap-record-step/ap-record-header.tsx new file mode 100644 index 0000000..02b6bfd --- /dev/null +++ b/ui/src/views/access-point/ap-record-step/ap-record-header.tsx @@ -0,0 +1,29 @@ +import { Card, Icon, IconButton, Stepper } from '@/components'; + +export const APRecordCardHeader: React.FC = () => { + const { prevStep } = Stepper.useContext(); + + return ( + } + css={{ mr: '$2' }} + onClick={prevStep} + /> + } + rightIcon={ + } + /> + } + /> + ); +}; diff --git a/ui/src/views/access-point/ap-record-step/ap-record-step.tsx b/ui/src/views/access-point/ap-record-step/ap-record-step.tsx new file mode 100644 index 0000000..a103fb6 --- /dev/null +++ b/ui/src/views/access-point/ap-record-step/ap-record-step.tsx @@ -0,0 +1,13 @@ +import { Card } from '@/components'; + +import { APRecordCardBody } from './ap-record-body'; +import { APRecordCardHeader } from './ap-record-header'; + +export const APRecordStep: React.FC = () => { + return ( + + + + + ); +}; diff --git a/ui/src/views/access-point/ap-record-step/index.ts b/ui/src/views/access-point/ap-record-step/index.ts new file mode 100644 index 0000000..431877c --- /dev/null +++ b/ui/src/views/access-point/ap-record-step/index.ts @@ -0,0 +1 @@ +export * from './ap-record-step'; diff --git a/ui/src/views/access-point/ap-record-step/record-step.utils.ts b/ui/src/views/access-point/ap-record-step/record-step.utils.ts new file mode 100644 index 0000000..3e9370b --- /dev/null +++ b/ui/src/views/access-point/ap-record-step/record-step.utils.ts @@ -0,0 +1,5 @@ +export const isSubdomain = (url: string): boolean => { + const urlParts = url.split('.'); + + return urlParts.length > 2; +}; diff --git a/ui/src/views/access-point/create-ap-preview.tsx b/ui/src/views/access-point/create-ap-preview.tsx index d14330b..6ac9159 100644 --- a/ui/src/views/access-point/create-ap-preview.tsx +++ b/ui/src/views/access-point/create-ap-preview.tsx @@ -1,36 +1,71 @@ import { ethers } from 'ethers'; -import { useMemo } from 'react'; +import { useEffect, useMemo } from 'react'; +import { useAccount } from 'wagmi'; import { Button, Card, Flex, - Grid, Icon, IconButton, + ResolvedAddress, Stepper, + Text, } from '@/components'; import { useTransactionCost } from '@/hooks'; import { FleekERC721 } from '@/integrations'; +import { AppLog } from '@/utils'; +import { useAccessPointFormContext } from './ap-form-step/create-ap.form.context'; +import { SelectedNFA } from './ap-form-step/create-ap-form-body'; import { CreateAccessPoint } from './create-ap.context'; -import { useAccessPointFormContext } from './create-ap.form.context'; +import { DisplayText } from './display-text'; + +export const AccessPointDataFragment: React.FC = () => { + const { address, status } = useAccount(); + const { + form: { + domain: { + value: [domain], + }, + }, + } = useAccessPointFormContext(); + + if (status === 'connecting') return
Loading...
; //TODO replace with spinner + + return ( + <> + + {address || ''} + ) : ( + 'Please connect to wallet' + ) + } + /> + + + ); +}; export const CreateAccessPointPreview: React.FC = () => { const { prevStep } = Stepper.useContext(); + const { address } = useAccount(); + const { prepare: { status: prepareStatus, data: prepareData, error: prepareError }, write: { status: writeStatus, write }, transaction: { status: transactionStatus }, } = CreateAccessPoint.useTransactionContext(); + const { form: { - appName: { - value: [appName], - }, + isValid: [isValid], }, } = useAccessPointFormContext(); - const { nfa } = CreateAccessPoint.useContext(); const [cost, currency, isCostLoading] = useTransactionCost( prepareData?.request.value, @@ -66,10 +101,19 @@ export const CreateAccessPointPreview: React.FC = () => { [prepareStatus, writeStatus, transactionStatus] ); + useEffect(() => { + const error = [writeStatus, transactionStatus].some( + (status) => status === 'error' + ); + if (error) { + AppLog.errorToast('An error occurred while minting the NFA'); + } + }, [writeStatus, transactionStatus]); + return ( { } /> - - - NFA: {nfa.value} - {appName} - {message} - + + + {message} - + ); diff --git a/ui/src/views/access-point/create-ap-success.tsx b/ui/src/views/access-point/create-ap-success.tsx new file mode 100644 index 0000000..6758e67 --- /dev/null +++ b/ui/src/views/access-point/create-ap-success.tsx @@ -0,0 +1,47 @@ +import { Button, Card, Flex, Icon, IconButton, Text } from '@/components'; + +import { CreateAccessPoint } from './create-ap.context'; +import { AccessPointDataFragment } from './create-ap-preview'; + +export const CreateAccessPointSuccess: React.FC = () => { + const { nfa } = CreateAccessPoint.useContext(); + return ( + + + } + rightIcon={ + } + /> + } + /> + + + + {`You have successfully hosted a ${nfa.name} frontend on your own domain.`} + + + + + + + + + + ); +}; diff --git a/ui/src/views/access-point/create-ap.context.tsx b/ui/src/views/access-point/create-ap.context.tsx index 6dcf9bb..119f29c 100644 --- a/ui/src/views/access-point/create-ap.context.tsx +++ b/ui/src/views/access-point/create-ap.context.tsx @@ -1,16 +1,23 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { useState } from 'react'; -import { Token } from '@/graphclient'; import { EthereumHooks } from '@/integrations'; import { useFleekERC721Billing } from '@/store'; -import { AppLog, createContext, pushToast } from '@/utils'; +import { AppLog, createContext } from '@/utils'; -type NFA = Pick; +export type NFA = { + tokenId: string; + name: string; + logo: string; + color: number; + domain: string; +}; export type AccessPointContext = { billing: string | undefined; - nfa: NFA | undefined; - setNfa: ReactState[1]; + nfa: NFA; + setNfa: (nfa: NFA) => void; }; const [CreateAPProvider, useContext] = createContext({ @@ -31,7 +38,13 @@ export abstract class CreateAccessPoint { children, }) => { const [billing] = useFleekERC721Billing('AddAccessPoint'); - const [nfa, setNfa] = useState(); + const [nfa, setNfa] = useState({ + tokenId: '', + name: '', + logo: '', + color: 0, + domain: '', + }); const value = { billing, @@ -44,12 +57,10 @@ export abstract class CreateAccessPoint { { + onSuccess: (data: any) => { AppLog.info('Transaction:', data); - pushToast('success', 'Your transaction was successful!'); }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onError: (error) => { + onError: (error: any) => { AppLog.errorToast( 'There was an error trying to create the Access Point. Please try again' ); diff --git a/ui/src/views/access-point/create-ap.form-body.tsx b/ui/src/views/access-point/create-ap.form-body.tsx deleted file mode 100644 index 1180c3c..0000000 --- a/ui/src/views/access-point/create-ap.form-body.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useQuery } from '@apollo/client'; -import { ethers } from 'ethers'; -import { useEffect } from 'react'; -import { useParams } from 'react-router-dom'; - -import { Button, Flex, Form, Spinner, Stepper } from '@/components'; -import { getNFADocument } from '@/graphclient'; -import { AppLog } from '@/utils'; - -import { CreateAccessPoint } from './create-ap.context'; -import { useAccessPointFormContext } from './create-ap.form.context'; -import { NfaPicker } from './nfa-picker'; - -export const CreateAccessPointFormBody: React.FC = () => { - const { id } = useParams(); - const { nextStep } = Stepper.useContext(); - const { nfa, setNfa, billing } = CreateAccessPoint.useContext(); - const { setArgs } = CreateAccessPoint.useTransactionContext(); - - const { - form: { - appName: { - value: [appName], - }, - isValid: [isValid], - }, - } = useAccessPointFormContext(); - - const { - form: { appName: appNameContext }, - } = useAccessPointFormContext(); - - const { - data: nfaData, - error: nfaError, - loading: nfaLoading, - } = useQuery(getNFADocument, { - skip: id === undefined, - variables: { - id: ethers.utils.hexlify(Number(id)), - }, - }); - - useEffect(() => { - if (nfaError) { - AppLog.errorToast('Error fetching NFA'); - } - }, [nfaError]); - - useEffect(() => { - if (nfaData) { - if (nfaData.token && id) { - const { name } = nfaData.token; - setNfa({ value: id, label: name }); - } else { - AppLog.errorToast("We couldn't find the NFA you are looking for"); - } - } - }, [nfaData, id, setNfa]); - - if (nfaLoading) { - return ( - - - - ); - } - - const handleContinueClick = (): void => { - if (nfa && appName) { - setArgs([Number(nfa.value), appName, { value: billing }]); - nextStep(); - } - }; - - return ( - <> - {/* TODO will have to do some changes on the Form.Combobox if we use this component for the NFA picker */} - {id === undefined && } - - App Name - - - - - ); -}; diff --git a/ui/src/views/access-point/create-ap.stepper.tsx b/ui/src/views/access-point/create-ap.stepper.tsx index bdc3f3f..89c7355 100644 --- a/ui/src/views/access-point/create-ap.stepper.tsx +++ b/ui/src/views/access-point/create-ap.stepper.tsx @@ -1,16 +1,29 @@ import { Form, Step, Stepper } from '@/components'; import { WalletStep } from '../mint/wallet-step'; -import { useAccessPointFormContext } from './create-ap.form.context'; -import { CreateAccessPointForm } from './create-ap-form'; +import { useAccessPointFormContext } from './ap-form-step/create-ap.form.context'; +import { CreateAccessPointForm } from './ap-form-step/create-ap-form'; +import { APRecordStep } from './ap-record-step/ap-record-step'; +import { isSubdomain } from './ap-record-step/record-step.utils'; +import { CreateAccessPoint } from './create-ap.context'; import { CreateAccessPointPreview } from './create-ap-preview'; +import { CreateAccessPointSuccess } from './create-ap-success'; export const CreateApStepper: React.FC = () => { + const { + transaction: { isSuccess }, + } = CreateAccessPoint.useTransactionContext(); const { form: { + domain: { + value: [accesPointDomain], + }, isValid: [, setIsValid], }, } = useAccessPointFormContext(); + + if (isSuccess) return ; + return ( @@ -22,13 +35,23 @@ export const CreateApStepper: React.FC = () => { - + - + + + + + + + diff --git a/ui/src/views/access-point/create-ap.tsx b/ui/src/views/access-point/create-ap.tsx index 446ee0b..3240bdc 100644 --- a/ui/src/views/access-point/create-ap.tsx +++ b/ui/src/views/access-point/create-ap.tsx @@ -1,10 +1,10 @@ import { Flex } from '@/components'; -import { CreateAccessPoint } from './create-ap.context'; import { CreateAccessPointFormProvider, useAccessPointFormContextInit, -} from './create-ap.form.context'; +} from './ap-form-step/create-ap.form.context'; +import { CreateAccessPoint } from './create-ap.context'; import { CreateApStepper } from './create-ap.stepper'; export const CreateAP: React.FC = () => { diff --git a/ui/src/views/access-point/display-text/display-text.styles.ts b/ui/src/views/access-point/display-text/display-text.styles.ts new file mode 100644 index 0000000..644be97 --- /dev/null +++ b/ui/src/views/access-point/display-text/display-text.styles.ts @@ -0,0 +1,25 @@ +import { styled } from '@/theme'; + +import { Flex } from '../../../components/layout'; + +export const DisplayTextStyles = { + Container: styled(Flex, { + flexDirection: 'column', + }), + Label: styled('label', { + color: '$slate11', + mb: '$1h', + + fontSize: '$xs', + //TODO add variants + }), + Input: styled('span', { + backgroundColor: '$slate1', + borderColor: '$slate1', + color: '$slate12', + borderRadius: '$lg', + fontSize: '$sm', + height: '$11', + p: '$3 $3h', + }), +}; diff --git a/ui/src/views/access-point/display-text/display-text.tsx b/ui/src/views/access-point/display-text/display-text.tsx new file mode 100644 index 0000000..5c6f7de --- /dev/null +++ b/ui/src/views/access-point/display-text/display-text.tsx @@ -0,0 +1,17 @@ +import { DisplayTextStyles as S } from './display-text.styles'; +type DisplayTextProps = { + label: string; + value: string | React.ReactNode; +}; + +export const DisplayText: React.FC = ({ + label, + value, +}: DisplayTextProps) => { + return ( + + {label} + {value} + + ); +}; diff --git a/ui/src/views/access-point/display-text/index.ts b/ui/src/views/access-point/display-text/index.ts new file mode 100644 index 0000000..8b8941a --- /dev/null +++ b/ui/src/views/access-point/display-text/index.ts @@ -0,0 +1 @@ +export * from './display-text'; diff --git a/ui/src/views/access-point/nfa-icon/index.ts b/ui/src/views/access-point/nfa-icon/index.ts new file mode 100644 index 0000000..c4e6ccc --- /dev/null +++ b/ui/src/views/access-point/nfa-icon/index.ts @@ -0,0 +1 @@ +export * from './nfa-icon'; diff --git a/ui/src/views/access-point/nfa-icon/nfa-icon.styles.ts b/ui/src/views/access-point/nfa-icon/nfa-icon.styles.ts new file mode 100644 index 0000000..39e91c7 --- /dev/null +++ b/ui/src/views/access-point/nfa-icon/nfa-icon.styles.ts @@ -0,0 +1,15 @@ +import { styled } from '@/theme'; + +export const NFAIconStyles = { + Container: styled('span', { + p: '$1h', + borderRadius: '$full', + width: '$7', + height: '$7', + mr: '$2', + }), + Image: styled('img', { + width: '100%', + height: '100%', + }), +}; diff --git a/ui/src/views/access-point/nfa-icon/nfa-icon.tsx b/ui/src/views/access-point/nfa-icon/nfa-icon.tsx new file mode 100644 index 0000000..3caddb1 --- /dev/null +++ b/ui/src/views/access-point/nfa-icon/nfa-icon.tsx @@ -0,0 +1,21 @@ +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/access-point/nfa-picker.tsx b/ui/src/views/access-point/nfa-picker.tsx deleted file mode 100644 index e25b520..0000000 --- a/ui/src/views/access-point/nfa-picker.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useQuery } from '@apollo/client'; -import { useMemo } from 'react'; - -import { Combobox } from '@/components'; -import { getLatestNFAsDocument } from '@/graphclient'; -import { AppLog } from '@/utils'; - -import { CreateAccessPoint } from './create-ap.context'; - -export const NfaPicker: React.FC = () => { - const { nfa, setNfa } = CreateAccessPoint.useContext(); - const { data, loading, error } = useQuery(getLatestNFAsDocument); - - const items = useMemo(() => data?.tokens || [], [data]); - - if (error) { - AppLog.errorToast('Error loading NFA list', error); - } - - return ( - - {({ Field, Options }) => ( - <> - {(selected) => selected?.name || 'Select NFA'} - - {(item) => item.name} - - )} - - ); -}; diff --git a/ui/src/views/components-test/components-test.tsx b/ui/src/views/components-test/components-test.tsx index ec9655f..d20a2c1 100644 --- a/ui/src/views/components-test/components-test.tsx +++ b/ui/src/views/components-test/components-test.tsx @@ -2,13 +2,15 @@ import { Flex, ResolvedAddress } from '@/components'; import { ColorPickerTest } from './color-picker'; import { ComboboxTest } from './combobox-test'; +import { SpinnerTest } from './spinner-test'; import { ToastTest } from './toast-test'; export const ComponentsTest: React.FC = () => { return ( + - {'0x7ed735b7095c05d78df169f991f2b7f1a1f1a049'} + {'0x7ed735b7095c05d78df169f991f2b7f1a1f1a049a'} diff --git a/ui/src/views/components-test/spinner-test.tsx b/ui/src/views/components-test/spinner-test.tsx new file mode 100644 index 0000000..d24b6a8 --- /dev/null +++ b/ui/src/views/components-test/spinner-test.tsx @@ -0,0 +1,18 @@ +import { Flex, Spinner, SpinnerDot } from '@/components'; + +export const SpinnerTest: React.FC = () => { + return ( + <> + + + + + + + + + + + + ); +}; diff --git a/ui/src/views/explore/explore.styles.ts b/ui/src/views/explore/explore.styles.ts index a539975..c2ec010 100644 --- a/ui/src/views/explore/explore.styles.ts +++ b/ui/src/views/explore/explore.styles.ts @@ -4,7 +4,7 @@ import { styled } from '@/theme'; export abstract class Explore { static readonly Container = styled(Flex, { flexDirection: 'column', - width: '64.75rem', //TODO replace for max-width + width: '63.4rem', //TODO replace for max-width margin: '0 auto', }); } diff --git a/ui/src/views/mint/github-step/steps/github-repo-configuration/repo-configuration-body/repo-configuration-body.tsx b/ui/src/views/mint/github-step/steps/github-repo-configuration/repo-configuration-body/repo-configuration-body.tsx index 5943ca7..26569a9 100644 --- a/ui/src/views/mint/github-step/steps/github-repo-configuration/repo-configuration-body/repo-configuration-body.tsx +++ b/ui/src/views/mint/github-step/steps/github-repo-configuration/repo-configuration-body/repo-configuration-body.tsx @@ -1,4 +1,4 @@ -import { Button, Card, Flex, Stepper } from '@/components'; +import { Button, Card, CardTag, Flex, Stepper } from '@/components'; import { Mint } from '@/views/mint/mint.context'; import { useMintFormContext } from '@/views/mint/nfa-step/form-step'; @@ -25,17 +25,8 @@ export const RepoConfigurationBody: React.FC = () => { - Use for NFA - - } + css={{ mb: '0', cursor: 'default' }} + button={Use for NFA} />