mirror of
https://github.com/MetaFam/TheGame.git
synced 2026-04-24 03:00:09 -04:00
refactor: using next api for fetching metadata + eslint updates
This commit is contained in:
committed by
Scott Stevenson
parent
7222670c1c
commit
10f66448ea
20
package.json
20
package.json
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
18
packages/web/lib/hooks/useDelay.ts
Normal file
18
packages/web/lib/hooks/useDelay.ts
Normal 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;
|
||||
};
|
||||
@@ -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",
|
||||
|
||||
22
packages/web/pages/api/metadata.tsx
Normal file
22
packages/web/pages/api/metadata.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user