feat: minted site detail (#36)

* feat: add detail view. add home button component. add loading component

* feat: add tile info component

* style: change bg color and front family

* refactor: remove old file

* style: new style for detail view

* chore: fix comments

* refactor: change SiteNFTDetail type

* refactor: PR comments changes

Co-authored-by: Felipe Mendes <zo.fmendes@gmail.com>
This commit is contained in:
Camila Sosa Morales 2022-12-14 18:28:15 -03:00 committed by GitHub
parent 09d24f9723
commit 6451208871
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 481 additions and 7 deletions

View File

@ -37,6 +37,7 @@
"eslint-plugin-react": "^7.31.11",
"ethers": "^5.7.2",
"prettier": "^2.8.0",
"react-query": "^3.39.2",
"ts-loader": "^9.4.1",
"typescript": "^4.9.3",
"vite": "^3.2.4",

View File

@ -1,6 +1,6 @@
import React from 'react';
import { BrowserRouter, Route, Routes, Navigate } from 'react-router-dom';
import { Home, MintSite } from './views';
import { Home, MintSite, MintedSiteDetail } from './views';
export const App = () => {
return (
@ -8,6 +8,7 @@ export const App = () => {
<Routes>
<Route path="/mint-site" element={<MintSite />} />
<Route path="/home" element={<Home />} />
<Route path="/detail" element={<MintedSiteDetail />} />
<Route path="*" element={<Navigate to="/home" />} />
</Routes>
</BrowserRouter>

View File

@ -0,0 +1,34 @@
import {
AccordionItem as AccordionItemChakra,
AccordionButton,
Box,
Heading,
AccordionIcon,
AccordionPanel,
AccordionPanelProps,
forwardRef,
} from '@chakra-ui/react';
import React from 'react';
type AccordionProps = AccordionPanelProps & {
children: React.ReactNode;
heading: string;
};
export const AccordionItem = forwardRef<AccordionProps, 'div'>(
({ children, heading, ...panelProps }, ref) => {
return (
<AccordionItemChakra>
<AccordionButton borderBottomWidth="1px">
<Box flex="1" textAlign="left">
<Heading size="md"> {heading}</Heading>
</Box>
<AccordionIcon />
</AccordionButton>
<AccordionPanel ref={ref} {...panelProps} pb={4} overflowY="scroll">
{children}
</AccordionPanel>
</AccordionItemChakra>
);
}
);

View File

@ -0,0 +1,2 @@
export * from './accordion-item';

View File

@ -0,0 +1,30 @@
import { SiteNFTDetail } from '@/types';
import { HStack } from '@chakra-ui/react';
import { CardAttributes } from '../card';
type AttributesDetailProps = {
owner: string;
attributes: SiteNFTDetail['attributes'];
tokendId: string;
};
export const AttributesDetail = ({
owner,
attributes,
tokendId,
}: AttributesDetailProps) => {
return (
<HStack shouldWrapChildren display="inline" spacing="0px">
<CardAttributes heading="Owner" info={owner} />
{attributes.map((attribute) => (
<CardAttributes
key={attribute.trait_type}
heading={attribute.trait_type}
info={attribute.value}
/>
))}
<CardAttributes heading="Token ID" info={tokendId} />
</HStack>
);
};

View File

@ -0,0 +1 @@
export * from './attributes-detail';

View File

@ -0,0 +1,23 @@
import { Card, CardBody } from '@chakra-ui/react';
import { TileInfo } from '../tile-info';
type CardAttributesProps = {
heading: string;
info: string;
};
export const CardAttributes = ({ heading, info }: CardAttributesProps) => (
<Card
mr="10px"
mb="5px"
direction={{ base: 'column', sm: 'row' }}
overflow="hidden"
variant="outline"
width="200px"
>
<CardBody width="200px">
<TileInfo size="sm" heading={heading} info={info} width={160} />
</CardBody>
</Card>
);

View File

@ -0,0 +1,2 @@
export * from './card-attributes';

View File

@ -0,0 +1,18 @@
import { ArrowBackIcon } from '@chakra-ui/icons';
import { IconButton } from '@chakra-ui/react';
import { Link } from 'react-router-dom';
export const HomeButton = () => {
return (
<IconButton
as={Link}
to="/home"
aria-label="back home"
icon={<ArrowBackIcon />}
variant="link"
size={'xl'}
textDecoration={'none'}
/>
);
};

View File

@ -0,0 +1 @@
export * from './home-button';

View File

@ -0,0 +1,22 @@
import { forwardRef, Image, ImageProps } from '@chakra-ui/react';
type ImagePreviewProps = ImageProps & {
image: string;
};
export const ImagePreview = forwardRef<ImagePreviewProps, 'img'>(
({ image, ...imageProps }, ref) => {
return (
<>
{/* TODO add fallback Image */}
<Image
ref={ref}
src={image}
{...imageProps}
fallbackSrc="https://via.placeholder.com/150"
/>
</>
);
}
);

View File

@ -0,0 +1 @@
export * from './image-preview';

View File

@ -0,0 +1,9 @@
export * from './loading';
export * from './home-button';
export * from './image-preview';
export * from './tile-info';
export * from './card';
export * from './accordion-item';
export * from './input-field-form';
export * from './attributes-detail';

View File

@ -0,0 +1 @@
export * from './loading';

View File

@ -0,0 +1,16 @@
import { Flex, Spinner } from '@chakra-ui/react';
export const Loading = () => {
return (
<Flex justifyContent="center" height="80vh" alignItems="center">
<Spinner
thickness="3px"
speed="0.65s"
emptyColor="gray.200"
color="blue.500"
size="xl"
/>
</Flex>
);
};

View File

@ -0,0 +1 @@
export * from './tile-info';

View File

@ -0,0 +1,33 @@
import {
Flex,
forwardRef,
Heading,
HeadingProps,
Text,
} from '@chakra-ui/react';
type TileInfoProps = HeadingProps & {
heading: string;
info: string;
width?: number;
};
export const TileInfo = forwardRef<TileInfoProps, 'h2'>(
({ heading, info, width = 250, ...headingProps }, ref) => (
<Flex direction="column" alignItems="center">
<Heading ref={ref} {...headingProps}>
{heading}
</Heading>
<Text
width={width}
whiteSpace="nowrap"
overflow="hidden"
textOverflow="ellipsis"
textAlign="center"
>
{info}
</Text>
</Flex>
)
);

View File

@ -3,6 +3,9 @@ import ReactDOM from 'react-dom/client';
import { App } from './app';
import { ChakraProvider } from '@chakra-ui/react';
import { theme } from './theme';
import { QueryClient, QueryClientProvider } from 'react-query';
const queryClient = new QueryClient();
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
@ -11,7 +14,9 @@ const root = ReactDOM.createRoot(
root.render(
<React.StrictMode>
<ChakraProvider theme={theme} resetCSS>
<App />
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</ChakraProvider>
</React.StrictMode>
);

53
ui/src/mocks/detail.ts Normal file
View File

@ -0,0 +1,53 @@
const MINT_PARAMS = {
name: 'Fleek Test App',
description: 'Fleek Test App Description',
image: 'https://storageapi.fleek.co/fleek-team-bucket/site/fleek-logo.png',
ens: 'fleek.eth',
externalUrl: 'https://fleek.co',
commitHash: 'b72e47171746b6a9e29b801af9cb655ecf4d665c',
gitRepository: 'https://github.com/fleekxyz/contracts',
author: 'author',
};
const mockDetail = {
owner: '0x8f7b9e1b5f1f2c3c1f8b0b1b2e1b2f1f2c3c1f8b',
name: MINT_PARAMS.name,
description: MINT_PARAMS.description,
image: MINT_PARAMS.image,
external_url: MINT_PARAMS.externalUrl,
attributes: [
{
trait_type: 'ENS',
value: MINT_PARAMS.ens,
},
{
trait_type: 'Commit Hash',
value: MINT_PARAMS.commitHash,
},
{
trait_type: 'Repository',
value: MINT_PARAMS.gitRepository,
},
//As we're not showing this on the UI, we can remove it
// {
// trait_type: 'Author',
// value: MINT_PARAMS.author,
// },
// {
// trait_type: 'Version',
// value: '0',
// },
],
};
export const fetchSiteDetail = async (tokenId: string) => {
//TODO get site detail from api
return new Promise((resolved, reject) => {
setTimeout(() => {
resolved({
data: { ...mockDetail, externalUrl: mockDetail.external_url },
});
}, 2500);
});
};

View File

@ -1 +1,3 @@
export * from './mint-site';
export * from './mint-site';
export * from './detail';

View File

@ -3,7 +3,6 @@ import { SiteNFT } from '@/types';
export const mintSiteNFT = async (props: SiteNFT) => {
const { name, description, owner, externalUrl, ens, commitHash, repo } =
props;
console.log('mintSiteNFT', props);
return new Promise((resolved, rejected) => {
setTimeout(() => {
// returning data of the site for now

View File

@ -9,3 +9,12 @@ export type SiteNFT = {
repo: string;
};
export type SiteNFTDetail = Omit<SiteNFT, 'ens' | 'commitHash' | 'repo'> & {
attributes: [
{
trait_type: string;
value: string;
}
];
};

View File

@ -0,0 +1,125 @@
import { useSearchParams } from 'react-router-dom';
import { useQuery } from 'react-query';
import {
Accordion,
Box,
Card,
CardBody,
Flex,
Heading,
Link,
VStack,
} from '@chakra-ui/react';
import {
HomeButton,
ImagePreview,
AccordionItem,
Loading,
AttributesDetail,
} from '@/components';
import { ExternalLinkIcon } from '@chakra-ui/icons';
import { fetchSiteDetail } from '@/mocks';
import { ErrorScreen } from '@/views';
import { SiteNFTDetail } from '@/types';
export const MintedSiteDetail = () => {
const [searchParams] = useSearchParams();
const tokenIdParam = searchParams.get('tokenId');
//TODO handle response type
const { data, status } = useQuery('fetchDetail', () =>
fetchSiteDetail(tokenIdParam as string)
);
if (status === 'loading') {
return <Loading />;
}
if (status === 'error') {
return <ErrorScreen />;
}
const { owner, name, description, image, externalUrl, attributes } =
data.data as SiteNFTDetail;
return (
<>
<Flex width="full" align="center" justifyContent="center">
<Box width={{ base: '100%' }}>
<HomeButton />
<Box
flexDirection="row"
display="flex"
justifyContent="space-evenly"
mt={10}
>
<Box mr={5}>
<Box
display="flex"
flexDirection="row"
alignItems="flex-end"
mb={5}
>
<Heading mr={5}>{name}</Heading>
</Box>
<Card backgroundColor="transparent" border="1px">
<CardBody padding="1px 8px 10px 8px">
<Accordion defaultIndex={[0, 1]} allowMultiple width="45vw">
<AccordionItem
heading="Description"
minH={120}
maxH="auto"
children={<p>{description}</p>}
/>
<AccordionItem
heading="Attributes"
children={
<AttributesDetail
owner={owner}
attributes={attributes}
tokendId={tokenIdParam as string}
/>
}
padding="16px"
/>
</Accordion>
<Box ml={5} mt={2}>
<Link href={externalUrl} isExternal>
Visit site <ExternalLinkIcon mx="2px" />
</Link>
</Box>
</CardBody>
</Card>
</Box>
<VStack alignItems="flex-start">
<Box
border="1px"
width="-webkit-fill-available"
padding="5px 10px"
borderTopRadius={10}
>
<Heading size="md">Preview</Heading>
</Box>
<Box
mt="0px !important"
boxSize="md"
border="1px"
padding={10}
borderRadius={20}
borderTopRadius={0}
boxShadow="12px 10px 14px 6px #868686d1"
>
<ImagePreview
image={image}
width="auto"
height="auto"
maxW="100%"
maxH="100%"
/>
</Box>
</VStack>
</Box>
</Box>
</Flex>
</>
);
};

View File

@ -0,0 +1 @@
export * from './detail';

View File

@ -0,0 +1,9 @@
import { Flex, Heading } from '@chakra-ui/react';
export const ErrorScreen = () => {
return (
<Flex justifyContent="center" height="80vh" alignItems="center">
<Heading size="md">Something went wrong</Heading>
</Flex>
);
};

View File

@ -0,0 +1 @@
export * from './error-screen';

View File

@ -1,3 +1,5 @@
export * from './home';
export * from './mint-site';
export * from './detail';
export * from './error-screen';

View File

@ -1,2 +1,3 @@
export * from './mint-site';
export * from './mint-site.utils';

View File

@ -20,7 +20,7 @@ import { Link } from 'react-router-dom';
import { mintSiteNFT } from '@/mocks';
import { getRepoAndCommit } from '@/utils';
import { validateFields } from './mint-site.utils';
import { InputFieldForm } from './components';
import { InputFieldForm } from '@/components';
interface FormValues {
name: string;

View File

@ -206,7 +206,7 @@
"@babel/plugin-syntax-jsx" "^7.18.6"
"@babel/types" "^7.19.0"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3":
"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2":
version "7.20.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.6.tgz#facf4879bfed9b5326326273a64220f099b0fce3"
integrity sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==
@ -1998,6 +1998,11 @@ bech32@1.1.4:
resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9"
integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==
big-integer@^1.6.16:
version "1.6.51"
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686"
integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==
bn.js@^4.11.9:
version "4.12.0"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
@ -2023,6 +2028,20 @@ braces@^3.0.2:
dependencies:
fill-range "^7.0.1"
broadcast-channel@^3.4.1:
version "3.7.0"
resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-3.7.0.tgz#2dfa5c7b4289547ac3f6705f9c00af8723889937"
integrity sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==
dependencies:
"@babel/runtime" "^7.7.2"
detect-node "^2.1.0"
js-sha3 "0.8.0"
microseconds "0.2.0"
nano-time "1.0.0"
oblivious-set "1.0.0"
rimraf "3.0.2"
unload "2.2.0"
brorand@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
@ -2196,6 +2215,11 @@ detect-node-es@^1.1.0:
resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493"
integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==
detect-node@^2.0.4, detect-node@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
diff-sequences@^29.3.1:
version "29.3.1"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.3.1.tgz#104b5b95fe725932421a9c6e5b4bef84c3f2249e"
@ -3321,6 +3345,14 @@ magic-string@^0.26.7:
dependencies:
sourcemap-codec "^1.4.8"
match-sorter@^6.0.2:
version "6.3.1"
resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.1.tgz#98cc37fda756093424ddf3cbc62bfe9c75b92bda"
integrity sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==
dependencies:
"@babel/runtime" "^7.12.5"
remove-accents "0.4.2"
merge2@^1.3.0, merge2@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
@ -3334,6 +3366,11 @@ micromatch@^4.0.0, micromatch@^4.0.4:
braces "^3.0.2"
picomatch "^2.3.1"
microseconds@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39"
integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
@ -3370,6 +3407,13 @@ mz@^2.7.0:
object-assign "^4.0.1"
thenify-all "^1.0.0"
nano-time@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef"
integrity sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==
dependencies:
big-integer "^1.6.16"
nanoid@^3.3.4:
version "3.3.4"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
@ -3450,6 +3494,11 @@ object.values@^1.1.6:
define-properties "^1.1.4"
es-abstract "^1.20.4"
oblivious-set@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566"
integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@ -3664,6 +3713,15 @@ react-is@^18.0.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
react-query@^3.39.2:
version "3.39.2"
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.2.tgz#9224140f0296f01e9664b78ed6e4f69a0cc9216f"
integrity sha512-F6hYDKyNgDQfQOuR1Rsp3VRzJnWHx6aRnnIZHMNGGgbL3SBgpZTDg8MQwmxOgpCAoqZJA+JSNCydF1xGJqKOCA==
dependencies:
"@babel/runtime" "^7.5.5"
broadcast-channel "^3.4.1"
match-sorter "^6.0.2"
react-refresh@^0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e"
@ -3749,6 +3807,11 @@ regexpp@^3.2.0:
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
remove-accents@0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5"
integrity sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@ -3777,7 +3840,7 @@ reusify@^1.0.4:
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
rimraf@^3.0.2:
rimraf@3.0.2, rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
@ -4096,6 +4159,14 @@ unbox-primitive@^1.0.2:
has-symbols "^1.0.3"
which-boxed-primitive "^1.0.2"
unload@2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7"
integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==
dependencies:
"@babel/runtime" "^7.6.2"
detect-node "^2.0.4"
update-browserslist-db@^1.0.9:
version "1.0.10"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3"