refactor: using next api for fetching metadata + eslint updates

This commit is contained in:
dan13ram
2022-02-04 17:48:39 +05:30
committed by Scott Stevenson
parent 7222670c1c
commit 10f66448ea
10 changed files with 667 additions and 508 deletions

View File

@@ -55,27 +55,27 @@
"@types/node": "15.6.1",
"@types/react": "17.0.6",
"@types/react-dom": "17.0.5",
"@typescript-eslint/eslint-plugin": "5.5.0",
"@typescript-eslint/parser": "5.4.0",
"@typescript-eslint/eslint-plugin": "5.10.2",
"@typescript-eslint/parser": "5.10.2",
"env-cmd": "10.1.0",
"eslint": "7.32.0",
"eslint-config-airbnb": "19.0.2",
"eslint": "8.8.0",
"eslint-config-airbnb": "19.0.4",
"eslint-config-airbnb-typescript": "16.1.0",
"eslint-config-prettier": "8.3.0",
"eslint-import-resolver-typescript": "2.5.0",
"eslint-plugin-import": "2.23.4",
"eslint-plugin-jest": "24.3.6",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.27.1",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-jest": "26.0.0",
"eslint-plugin-jsx-a11y": "6.5.1",
"eslint-plugin-react": "7.28.0",
"eslint-plugin-react-hooks": "4.3.0",
"eslint-plugin-simple-import-sort": "7.0.0",
"get-graphql-schema": "2.1.2",
"graphql": "15.8.0",
"hasura-cli": "1.3.3",
"husky": "6.0.0",
"husky": "7.0.4",
"jest": "26.6.3",
"lerna": "4.0.0",
"lint-staged": "11.0.0",
"lint-staged": "12.3.3",
"prettier": "2.2.1",
"react": "17.0.2",
"react-dom": "17.0.2",

View File

@@ -58,7 +58,6 @@
},
"devDependencies": {
"concurrently": "6.1.0",
"eslint-plugin-jest": "24.3.6",
"nock": "13.2.1",
"ts-node-dev": "1.1.6"
},

View File

@@ -42,8 +42,6 @@
"@babel/core": "7.14.2",
"babel-loader": "8.2.2",
"react-dom": "17.0.2",
"react-google-font-loader": "1.1.0",
"typescript": "4.5.4",
"tslib": "2.2.0"
"react-google-font-loader": "1.1.0"
}
}

View File

@@ -16,7 +16,6 @@ import {
} from '@metafam/ds';
import { Maybe } from '@metafam/utils';
import BackgroundImage from 'assets/main-background.jpg';
import { FlexContainer } from 'components/Container';
import { PlayerSection } from 'components/Profile/PlayerSection';
import { PlayerFragmentFragment } from 'graphql/autogen/types';
import { PersonalityInfo } from 'graphql/queries/enums/getPersonalityInfo';
@@ -90,31 +89,30 @@ export const PlayerAddSection = React.forwardRef<HTMLDivElement, Props>(
bgImage={`url(${BackgroundImage})`}
bgSize="cover"
bgAttachment="fixed"
p={[4, 8, 12]}
px={[4, 8, 12]}
py={8}
>
<FlexContainer>
<ModalHeader
color="white"
fontSize="4xl"
alignSelf="center"
fontWeight="normal"
>
Add New Block
</ModalHeader>
<VStack spacing={0} align="center">
<ModalCloseButton
color="pinkShadeOne"
size="xl"
p={4}
top={0}
right={0}
_focus={{
boxShadow: 'none',
}}
/>
<ModalHeader color="white" fontSize="4xl" fontWeight="normal">
Add New Block
</ModalHeader>
<ModalBody>
<VStack
spacing="6"
spacing={6}
color="white"
w={{ base: '100%', sm: '30rem' }}
maxW="30rem"
minH="30rem"
>
<Select
css={{
@@ -194,7 +192,7 @@ export const PlayerAddSection = React.forwardRef<HTMLDivElement, Props>(
Close
</Button>
</ModalFooter>
</FlexContainer>
</VStack>
</ModalContent>
</Modal>
</Flex>

View File

@@ -7,10 +7,12 @@ import {
Text,
} from '@metafam/ds';
import { ProfileSection } from 'components/Profile/ProfileSection';
import React, { useEffect, useRef, useState } from 'react';
import { Maybe } from 'graphql/autogen/types';
import { useDelay } from 'lib/hooks/useDelay';
import React, { useCallback, useEffect, useState } from 'react';
import { BoxType } from 'utils/boxTypes';
const proxyLink = 'https://rlp-proxy.herokuapp.com/v2?url='; // TODO: deploy our own proxy (makes use of the Twitter API)
const metadataLink = '/api/metadata?url=';
type EmbeddedUrlProps = {
url?: string;
@@ -18,180 +20,109 @@ type EmbeddedUrlProps = {
};
export const EmbeddedUrl: React.FC<EmbeddedUrlProps> = ({ url, canEdit }) => (
<ProfileSection canEdit={canEdit} boxType={BoxType.EMBEDDED_URL} withoutBG>
<LinkPreview
className="linkPreview"
height="100%"
width="100%"
customLoader={<LoadingState />}
showLoader
{...{ url, canEdit }}
/>
<ProfileSection
canEdit={canEdit}
boxType={BoxType.EMBEDDED_URL}
pb={0}
withoutBG
>
<LinkPreview {...{ url, canEdit }} />
</ProfileSection>
);
interface LinkPreviewProps {
url?: string;
className?: string;
width?: string | number;
height?: string | number;
descriptionLength?: number;
borderRadius?: string | number;
textAlign?: 'left' | 'right' | 'center';
margin?: string | number;
fallback?: JSX.Element[] | JSX.Element | null;
primaryTextColor?: string;
secondaryTextColor?: string;
showLoader?: boolean;
customLoader?: JSX.Element[] | JSX.Element | null;
canEdit?: boolean;
}
interface APIResponse {
title: string | null;
description: string | null;
image: string | null;
siteName: string | null;
hostname: string | null;
interface URIMetadata {
url?: Maybe<string>;
title?: Maybe<string>;
description?: Maybe<string>;
image?: Maybe<string>;
site_name?: Maybe<string>;
}
const LinkPreview: React.FC<LinkPreviewProps> = ({
url = '',
className = '',
width,
height,
borderRadius = 'lg',
textAlign,
margin,
fallback = null,
secondaryTextColor = 'rgb(255,255,255)',
showLoader = true,
customLoader = null,
}) => {
const isMounted = useRef(true);
const [metadata, setMetadata] = useState<APIResponse | null>();
const LinkPreview: React.FC<LinkPreviewProps> = ({ url: inputUrl = '' }) => {
const [metadata, setMetadata] = useState<Maybe<URIMetadata>>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
isMounted.current = true;
const updateMetadata = useCallback((uri: string) => {
setLoading(true);
fetch(proxyLink + url)
fetch(metadataLink + uri)
.then((res) => res.json())
.then((res) => {
if (isMounted.current) {
setMetadata((res.metadata as unknown) as APIResponse);
setLoading(false);
}
.then(({ error, response }) => {
if (error) throw error;
setMetadata((response.og as unknown) as URIMetadata);
setLoading(false);
})
.catch((err: Error) => {
// eslint-disable-next-line no-console
console.error('No metadata could be found for the given URL.', err);
if (isMounted.current) {
setMetadata(null);
setLoading(false);
}
setMetadata(null);
setLoading(false);
});
}, []);
return () => {
isMounted.current = false;
};
}, [url]);
const delayedUpdate = useDelay(updateMetadata);
if (loading && showLoader) {
if (customLoader) {
return <Box py="2rem">{customLoader}</Box>;
}
useEffect(() => delayedUpdate(inputUrl), [inputUrl, delayedUpdate]);
if (!metadata || loading) {
return (
<Box>
<p>Loading...</p>
<Box
minH="18rem"
w="100%"
h="100%"
borderRadius="lg"
backdropFilter="blur(7px)"
borderWidth={0}
>
{loading && <LoadingState />}
</Box>
);
}
if (!metadata) {
return <>{fallback}</>;
}
const { image, description, title, siteName } = metadata;
const onClick = () => {
window.open(url, '_blank');
};
const { image, description, title, site_name: siteName, url } = metadata;
return (
<LinkBox
data-testid="container"
onClick={onClick}
className={`Container ${className}`}
sx={{
width,
height,
borderRadius,
textAlign,
margin,
backdropFilter: 'blur(7px)',
borderWidth: '0',
overflow: 'hidden',
'&:hover': {
cursor: 'pointer',
},
onClick={() => window.open(url ?? inputUrl, '_blank')}
minH="18rem"
w="100%"
h="100%"
borderRadius="lg"
backdropFilter="blur(7px)"
borderWidth={0}
overflow="hidden"
_hover={{
cursor: 'pointer',
}}
>
<Flex w="100%" h="100%" direction="column">
<Box
data-testid="image-container"
sx={{
backgroundImage: `url(${image})`,
height: 'auto',
flex: 1,
minH: '12rem',
width: '100%',
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
}}
className="Image"
bgImage={image ? `url(${image})` : 'none'}
height="auto"
h="12rem"
w="100%"
bgSize="cover"
bgRepeat="no-repeat"
bgPosition="center"
/>
<Box
sx={{
p: 4,
h3: {
fontSize: '14px',
fontWeight: '900',
a: {
color: 'white',
},
},
}}
>
<h3>
<LinkOverlay href={url} isExternal>
<Box p={4}>
<LinkOverlay href={url ?? inputUrl} isExternal>
<Text fontSize="md" fontWeight="900" color="white">
{title}
</LinkOverlay>
</h3>
</Text>
</LinkOverlay>
{description && (
<Box
data-testid="desc"
className="Description Secondary"
sx={{
color: secondaryTextColor,
fontSize: '12px',
mt: 1,
}}
>
<Text noOfLines={3}>{description}</Text>
<Box mt={1}>
<Text fontSize="sm" color="white" noOfLines={3}>
{description}
</Text>
</Box>
)}
<Box
className="Secondary SiteDetails"
sx={{
color: 'cyanText',
fontSize: '12px',
mt: 1,
}}
>
<Box mt={1} color="cyanText" fontSize="sm">
{siteName && <span>{siteName} </span>}
<Text isTruncated>{url}</Text>
</Box>

View File

@@ -1,5 +1,6 @@
import {
Box,
BoxProps,
Button,
EditIcon,
Flex,
@@ -35,7 +36,7 @@ export type ProfileSectionProps = {
modalTitle?: string;
modal?: React.ReactNode;
subheader?: string;
};
} & BoxProps;
export const ProfileSection: React.FC<ProfileSectionProps> = ({
children,
@@ -48,6 +49,7 @@ export const ProfileSection: React.FC<ProfileSectionProps> = ({
modal,
modalTitle,
subheader,
...props
}) => {
const { isOpen, onOpen, onClose } = useDisclosure();
@@ -124,6 +126,7 @@ export const ProfileSection: React.FC<ProfileSectionProps> = ({
pos="relative"
pointerEvents={canEdit ? 'none' : 'initial'}
pb={8}
{...props}
>
{children}
</Box>

View File

@@ -0,0 +1,18 @@
import { useCallback, useRef } from 'react';
// eslint-disable-next-line @typescript-eslint/ban-types
export const useDelay = (fn: Function, ms = 500): Function => {
const timer = useRef<ReturnType<typeof setTimeout>>();
const delayCallBack = useCallback(
(...args) => {
if (timer.current) {
clearTimeout(timer.current);
}
timer.current = setTimeout(fn.bind(this, ...args), ms);
},
[fn, ms],
);
return delayCallBack;
};

View File

@@ -53,7 +53,7 @@
"react-container-query": "0.12.0",
"react-dom": "17.0.2",
"react-draft-wysiwyg": "1.14.7",
"react-grid-layout": "1.3.0",
"react-grid-layout": "1.3.3",
"react-hook-form": "7.21.0",
"react-icons": "4.2.0",
"react-qr-svg": "2.3.0",
@@ -65,7 +65,8 @@
"swr": "1.0.1",
"urql": "2.0.2",
"web3.storage": "3.5.0",
"web3modal": "1.9.4"
"web3modal": "1.9.4",
"html-metadata-parser": "2.0.4"
},
"devDependencies": {
"@types/busboy": "0.3.1",

View File

@@ -0,0 +1,22 @@
import { parse } from 'html-metadata-parser';
import { NextApiRequest, NextApiResponse } from 'next';
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async function handler(req: NextApiRequest, res: NextApiResponse) {
const { url } = req.query;
if (req.method === 'GET') {
try {
const apiResponse = await parse(url as string, {
timeout: 2000,
});
return res.json({ error: null, response: apiResponse });
} catch (err) {
return res.json({ error: err, response: null });
}
} else {
return res.json({ error: 'Error: Incorrect Method', response: null });
}
}
export default handler;

869
yarn.lock

File diff suppressed because it is too large Load Diff