feat: NFA detail page subraph integration (#254)

* feat: nfa detail page with data from the subgraph

* Update ui/src/views/indexed-nfa/fragments/aside.fragment.tsx

Co-authored-by: Felipe Mendes <zo.fmendes@gmail.com>

---------

Co-authored-by: Felipe Mendes <zo.fmendes@gmail.com>
This commit is contained in:
Camila Sosa Morales 2023-05-09 10:31:29 -03:00 committed by GitHub
parent 85d14483cb
commit 393c4a316d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 136 additions and 119 deletions

View File

@ -38,7 +38,7 @@ query getLatestNFAs {
} }
} }
query getNFA($id: ID!) { query getNFADetail($id: ID!) {
token(id: $id) { token(id: $id) {
tokenId tokenId
owner { owner {
@ -50,6 +50,33 @@ query getNFA($id: ID!) {
externalURL externalURL
logo logo
color color
createdAt
accessPoints {
createdAt
contentVerified
owner {
id
}
id
}
verified
verifier {
id
}
gitRepository {
id
}
}
}
query getNFA($id: ID!) {
token(id: $id) {
tokenId
name
ENS
externalURL
logo
color
} }
} }

View File

@ -10,3 +10,45 @@ export const getRepoAndCommit = (url: string): object => {
export const contractAddress = (address: string): string => { export const contractAddress = (address: string): string => {
return `${address.slice(0, 6)}...${address.slice(-4)}`; return `${address.slice(0, 6)}...${address.slice(-4)}`;
}; };
export const getRepositoryFromURL = (url: string): string => {
const urlSplitted = url.split('/');
return `${urlSplitted[3]}/${urlSplitted[4]}`;
};
export const getDate = (date: number): string => {
return new Date(date * 1000).toLocaleDateString();
};
/**
* @param date date in tiemstamp format
* @returns time since date
*/
export const getTimeSince = (date: number): string => {
const now = new Date().getTime(); //in timestamp format
const milliseconds = now - date * 1000;
const seconds = milliseconds / 1000;
const minutes = Math.round((seconds / 60) % 60);
const days = Math.round(seconds / (60 * 60 * 24));
const hours = Math.round(minutes % 60);
const months = Math.round(days / 30.5);
const years = Math.round(months / 12);
if (years > 0) {
return `${years} year${years > 1 ? 's' : ''} ago`;
}
if (months > 0) {
return `${months} month${months > 1 ? 's' : ''} ago`;
}
if (days > 0) {
return `${days} day${days > 1 ? 's' : ''} ago`;
}
if (hours > 0) {
return `${hours} hour${hours > 1 ? 's' : ''} ago`;
}
if (minutes > 0) {
return `${minutes} min ago`;
}
return `${Math.round(seconds)} sec ago}`;
};

View File

@ -64,8 +64,8 @@ export const CreateAccessPointFormBody: React.FC = () => {
}, },
onCompleted(data) { onCompleted(data) {
if (data.token && id) { if (data.token && id) {
const { name, tokenId, logo, color, externalURL: domain } = data.token; const { name, tokenId, logo, color, externalURL } = data.token;
setNfa({ name, tokenId, logo, color, domain }); setNfa({ name, tokenId, logo, color, externalURL });
} else { } else {
AppLog.errorToast("We couldn't find the NFA you are looking for"); AppLog.errorToast("We couldn't find the NFA you are looking for");
} }

View File

@ -2,17 +2,15 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { useState } from 'react'; import { useState } from 'react';
import { Token } from '@/graphclient';
import { EthereumHooks } from '@/integrations'; import { EthereumHooks } from '@/integrations';
import { useFleekERC721Billing } from '@/store'; import { useFleekERC721Billing } from '@/store';
import { AppLog, createContext } from '@/utils'; import { AppLog, createContext } from '@/utils';
export type NFA = { export type NFA = Pick<
tokenId: string; Token,
name: string; 'tokenId' | 'name' | 'logo' | 'color' | 'externalURL'
logo: string; >;
color: number;
domain: string;
};
export type AccessPointContext = { export type AccessPointContext = {
billing: string | undefined; billing: string | undefined;
@ -43,7 +41,7 @@ export abstract class CreateAccessPoint {
name: '', name: '',
logo: '', logo: '',
color: 0, color: 0,
domain: '', externalURL: '',
}); });
const value = { const value = {

View File

@ -1,4 +1,4 @@
import { Flex, Icon, Input } from '@/components'; import { Flex, Icon } from '@/components';
import { styled } from '@/theme'; import { styled } from '@/theme';
export const NFASearchFragmentStyles = { export const NFASearchFragmentStyles = {

View File

@ -2,6 +2,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Button, Flex, Icon, NFAPreview } from '@/components'; import { Button, Flex, Icon, NFAPreview } from '@/components';
import { parseNumberToHexColor } from '@/utils/color';
import { IndexedNFA } from '../indexed-nfa.context'; import { IndexedNFA } from '../indexed-nfa.context';
import { IndexedNFAStyles as S } from '../indexed-nfa.styles'; import { IndexedNFAStyles as S } from '../indexed-nfa.styles';
@ -10,8 +11,7 @@ const Preview: React.FC = () => {
const { nfa } = IndexedNFA.useContext(); const { nfa } = IndexedNFA.useContext();
const color = useMemo( const color = useMemo(
// TODO: replace with util function () => `#${parseNumberToHexColor(nfa.color)}`,
() => `#${`000000${nfa.color.toString(16)}`.slice(-6)}`,
[nfa] [nfa]
); );

View File

@ -1,6 +1,7 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { Flex, Icon, IconName, ResolvedAddress, Text } from '@/components'; import { Flex, Icon, IconName, ResolvedAddress, Text } from '@/components';
import { getDate, getRepositoryFromURL, getTimeSince } from '@/utils';
import { IndexedNFA } from '../indexed-nfa.context'; import { IndexedNFA } from '../indexed-nfa.context';
import { IndexedNFAStyles as S } from '../indexed-nfa.styles'; import { IndexedNFAStyles as S } from '../indexed-nfa.styles';
@ -33,10 +34,7 @@ const Header: React.FC = () => {
<S.Main.Divider.Elipse /> <S.Main.Divider.Elipse />
<HeaderData label="Created"> <HeaderData label="Created">{getDate(nfa.createdAt)}</HeaderData>
{/* TODO: place correct data */}
12/12/22
</HeaderData>
<S.Main.Divider.Elipse /> <S.Main.Divider.Elipse />
@ -81,14 +79,12 @@ const DataWrapper: React.FC<DataWrapperProps> = ({
const Traits: React.FC = () => { const Traits: React.FC = () => {
const { nfa } = IndexedNFA.useContext(); const { nfa } = IndexedNFA.useContext();
// TODO: place correct data
const traitsToShow = useMemo(() => { const traitsToShow = useMemo(() => {
return [ return [
[nfa.ENS, 'ENS'], [nfa.ENS, 'ENS'],
[nfa.gitRepository.id, 'Repository'], [getRepositoryFromURL(nfa.gitRepository.id), 'Repository'],
[10, 'Version'], ['', 'Version'],
[nfa.externalURL, 'Domain'], [nfa.externalURL, 'Domain'],
[nfa.externalURL, 'Domain 2'],
]; ];
}, [nfa]); }, [nfa]);
@ -137,29 +133,33 @@ const VerificationBanner: React.FC<VerificationBannerProps> = ({
}; };
const Verification: React.FC = () => { const Verification: React.FC = () => {
const { nfa } = IndexedNFA.useContext();
return ( return (
<> <>
<S.Main.SectionHeading>Verification</S.Main.SectionHeading> <S.Main.SectionHeading>Verification</S.Main.SectionHeading>
{/* TODO: Get verified from context */} <VerificationBanner verified={nfa.verified} />
<VerificationBanner verified={Math.random() > 0.5} />
<S.Main.DataList> <S.Main.DataList>
{/* TODO: place correct data */} <DataWrapper label="Verifier">
<DataWrapper label="Verifier">polygon.eth</DataWrapper> {nfa.verifier ? (
<DataWrapper label="Repository">polygon/fe</DataWrapper> <ResolvedAddress>{nfa.verifier?.id}</ResolvedAddress>
) : (
'-'
)}
</DataWrapper>
<DataWrapper label="Repository">
{getRepositoryFromURL(nfa.gitRepository.id)}
</DataWrapper>
</S.Main.DataList> </S.Main.DataList>
</> </>
); );
}; };
// 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 = () => { const AccessPoints: React.FC = () => {
const {
nfa: { accessPoints },
} = IndexedNFA.useContext();
return ( return (
<> <>
<S.Main.SectionHeading>Frontends</S.Main.SectionHeading> <S.Main.SectionHeading>Frontends</S.Main.SectionHeading>
@ -184,86 +184,33 @@ const AccessPoints: React.FC = () => {
</S.Main.Table.Row> </S.Main.Table.Row>
</S.Main.Table.Head> </S.Main.Table.Head>
<S.Main.Table.Body> <S.Main.Table.Body>
{apMocks.map((item) => ( {accessPoints && accessPoints.length > 0 ? (
<S.Main.Table.Row key={item.domain}> accessPoints.map((item) => (
<S.Main.Table.Data align="center"> <S.Main.Table.Row key={item.id}>
<S.Main.Table.Marker <S.Main.Table.Data align="center">
variant={item.approved ? 'active' : 'inactive'} <S.Main.Table.Marker
/> variant={item.contentVerified ? 'active' : 'inactive'}
</S.Main.Table.Data> />
<S.Main.Table.Data>{item.domain}</S.Main.Table.Data> </S.Main.Table.Data>
<S.Main.Table.Data> <S.Main.Table.Data>{item.id}</S.Main.Table.Data>
<ResolvedAddress>{item.owner}</ResolvedAddress> <S.Main.Table.Data>
</S.Main.Table.Data> <ResolvedAddress>{item.owner.id}</ResolvedAddress>
<S.Main.Table.Data>{item.createdAt}</S.Main.Table.Data> </S.Main.Table.Data>
<S.Main.Table.Data> <S.Main.Table.Data>
<Icon name="external-link" /> {getTimeSince(item.createdAt)}
</S.Main.Table.Data>
<S.Main.Table.Data>
<Icon name="external-link" />
</S.Main.Table.Data>
</S.Main.Table.Row>
))
) : (
<S.Main.Table.Row>
<S.Main.Table.Data align="center" colSpan={5}>
<Text>No results</Text>
</S.Main.Table.Data> </S.Main.Table.Data>
</S.Main.Table.Row> </S.Main.Table.Row>
))} )}
</S.Main.Table.Body>
</S.Main.Table.Root>
</S.Main.Table.Container>
</>
);
};
// 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 (
<>
<S.Main.SectionHeading>Versions</S.Main.SectionHeading>
<S.Main.Table.Container>
<S.Main.Table.Root>
<colgroup>
<col span={1} style={{ width: '9.5%' }} />
<col span={1} style={{ width: '15%' }} />
<col span={1} style={{ width: '50%' }} />
<col span={1} style={{ width: '16%' }} />
<col span={1} style={{ width: '9.5%' }} />
</colgroup>
<S.Main.Table.Head>
<S.Main.Table.Row>
<S.Main.Table.Data>
<S.Main.Table.Marker />
</S.Main.Table.Data>
<S.Main.Table.Data>Commit</S.Main.Table.Data>
<S.Main.Table.Data>Preview</S.Main.Table.Data>
<S.Main.Table.Data>Time</S.Main.Table.Data>
<S.Main.Table.Data />
</S.Main.Table.Row>
</S.Main.Table.Head>
<S.Main.Table.Body>
{versionsMock.map((item) => (
<S.Main.Table.Row key={item.commit}>
<S.Main.Table.Data>
<S.Main.Table.Marker
variant={item.live ? 'active' : 'inactive'}
text={item.live}
>
{item.live && 'Live'}
</S.Main.Table.Marker>
</S.Main.Table.Data>
<S.Main.Table.Data>{item.commit.slice(0, 6)}</S.Main.Table.Data>
<S.Main.Table.Data title={item.preview}>
{item.preview}
</S.Main.Table.Data>
<S.Main.Table.Data>{item.time}</S.Main.Table.Data>
<S.Main.Table.Data>
<Icon name="external-link" />
</S.Main.Table.Data>
</S.Main.Table.Row>
))}
</S.Main.Table.Body> </S.Main.Table.Body>
</S.Main.Table.Root> </S.Main.Table.Root>
</S.Main.Table.Container> </S.Main.Table.Container>
@ -279,7 +226,6 @@ export const IndexedNFAMainFragment: React.FC = () => {
<Traits /> <Traits />
<Verification /> <Verification />
<AccessPoints /> <AccessPoints />
<Versions />
</S.Main.Container> </S.Main.Container>
); );
}; };

View File

@ -2,8 +2,7 @@ import { useQuery } from '@apollo/client';
import { ethers } from 'ethers'; import { ethers } from 'ethers';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { getNFADocument } from '@/graphclient'; import { getNFADetailDocument } from '@/graphclient';
import { NFAMock } from '@/mocks';
import { AppLog } from '@/utils'; import { AppLog } from '@/utils';
import { import {
@ -26,7 +25,7 @@ export const IndexedNFAView: React.FC = () => {
navigate('/', { replace: true }); navigate('/', { replace: true });
}; };
const { loading, data = { token: {} } } = useQuery(getNFADocument, { const { loading, data = { token: {} } } = useQuery(getNFADetailDocument, {
skip: id === undefined, skip: id === undefined,
variables: { variables: {
id: ethers.utils.hexlify(Number(id)), id: ethers.utils.hexlify(Number(id)),
@ -43,9 +42,13 @@ export const IndexedNFAView: React.FC = () => {
return <IndexedNFASkeletonFragment />; return <IndexedNFASkeletonFragment />;
} }
// TODO: replace NFAMock with real data from useQuery if (!data.token) {
//TODO add 404 page
return <div>Token not found</div>;
}
return ( return (
<IndexedNFA.Provider nfa={{ ...NFAMock, ...data.token }}> <IndexedNFA.Provider nfa={data.token}>
<S.Grid> <S.Grid>
<IndexedNFAAsideFragment /> <IndexedNFAAsideFragment />
<IndexedNFAMainFragment /> <IndexedNFAMainFragment />

View File

@ -10,6 +10,7 @@ import {
import { AppLog } from '@/utils'; import { AppLog } from '@/utils';
import { Mint } from '@/views/mint/mint.context'; import { Mint } from '@/views/mint/mint.context';
import { useMintFormContext } from '@/views/mint/nfa-step/form-step'; import { useMintFormContext } from '@/views/mint/nfa-step/form-step';
import { TextStyles } from './repo-branch-commit-fields.styles'; import { TextStyles } from './repo-branch-commit-fields.styles';
export const RepoBranchCommitFields: React.FC = () => { export const RepoBranchCommitFields: React.FC = () => {