diff --git a/.gitignore b/.gitignore index e6db162e5..cc191ab98 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ .tags* .tmp +.env +source/data node_modules/ dist/ .DS_Store diff --git a/package.json b/package.json index 64c77128c..3f86a3b28 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "dependencies": { "@babel/polyfill": "^7.4.0", "@babel/runtime": "^7.3.4", + "@rehooks/local-storage": "^2.1.1", "classnames": "^2.2.5", "color-convert": "^1.9.2", "core-js": "^3.2.1", @@ -59,6 +60,7 @@ "regenerator-runtime": "^0.13.3", "reselect": "^4.0.0", "screenfull": "^3.3.2", + "swr": "^0.1.16", "whatwg-fetch": "^3.0.0" }, "scripts": { @@ -135,6 +137,7 @@ "csv-loader": "^2.1.1", "daggy": "^1.3.0", "dedent-js": "^1.0.1", + "dotenv": "=8.1.0", "enzyme": "^3.3.0", "enzyme-adapter-react-16": "^1.1.1", "eslint": "^6.5.1", @@ -148,6 +151,7 @@ "http-server": "^0.11.1", "intl": "^1.2.5", "intl-locales-supported": "^1.0.0", + "isomorphic-fetch": "^2.2.1", "isomorphic-style-loader": "^5.1.0", "jest": "^24.9.0", "jest-transform-nearley": "^1.0.0", diff --git a/source/components/Banner.css b/source/components/Banner.css index acff8a89b..b536caca8 100644 --- a/source/components/Banner.css +++ b/source/components/Banner.css @@ -12,3 +12,29 @@ font-size: 1.4rem; margin-bottom: 0.6rem !important; } + +.ui__.banner.news { + justify-content: space-between; + background: var(--lightestColor); + font-size: 0.9em; + width: auto; + max-width: 450px; + margin: auto; + padding: 4px 15px 0px; + border-radius: 15px; +} + +.ui__.banner.news img { + vertical-align: middle !important; + top: 4px; + position: relative; +} + +.ui__.close-button { + cursor: pointer; + font-size: 1.3em; +} + +.ui__.close-button:hover { + color: black; +} diff --git a/source/components/ui/Typography.css b/source/components/ui/Typography.css index e66956de9..c00392b7d 100644 --- a/source/components/ui/Typography.css +++ b/source/components/ui/Typography.css @@ -107,6 +107,7 @@ a { font-size: inherit; padding: none; text-decoration: underline; + text-underline-offset: 4px; color: rgb(41, 117, 209); color: var(--color); } diff --git a/source/components/utils/markdown.tsx b/source/components/utils/markdown.tsx index 92a4ef118..e073d8328 100644 --- a/source/components/utils/markdown.tsx +++ b/source/components/utils/markdown.tsx @@ -30,7 +30,7 @@ export const Markdown = ({ ) diff --git a/source/scripts/fetch-releases.js b/source/scripts/fetch-releases.js new file mode 100644 index 000000000..2df5781a5 --- /dev/null +++ b/source/scripts/fetch-releases.js @@ -0,0 +1,101 @@ +// This script uses the GitHub API which requires an access token. +// https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line +// Once you have your access token you can put it in a `.env` file at the root +// of the project to enable it during development. For instance: +// +// GITHUB_API_SECRET=f4336c82cb1e494752d06e610614eab12b65f1d1 +// +require('dotenv').config() +require('isomorphic-fetch') +const fs = require('fs') +const path = require('path') + +// We use the GitHub API V4 in GraphQL to download the releases. A GraphQL +// explorer can be found here : https://developer.github.com/v4/explorer/ +const githubAuthToken = process.env.GITHUB_API_SECRET +const cursorOfV1Release = 'Y3Vyc29yOnYyOpHOARHb8g==' +const query = `query { + repository(owner:"betagouv", name:"mon-entreprise") { + releases(after:"${cursorOfV1Release}", last:100) { + nodes { + name + description + } + } + } +}` + +// In case we cannot fetch the release (the API is down or the Authorization +// token isn't valid) we fallback to some fake data -- it would be better to +// have a static ressource accessible without authentification. +const fakeData = [ + { + name: 'Fake release', + descriptionHTML: `You are seing this fake release because you + didn't configure your GitHub access token and we weren't + able to fetch the real releases from GitHub.

+ See the script
fetch-releases.js
for more informations.` + }, + { + name: 'Release 2', + descriptionHTML: 'blah blah blah' + }, + { + name: 'Release 3', + descriptionHTML: 'blah blah blah' + } +] + +// eslint-disable-next-line no-undef +const dataDir = path.resolve(__dirname, '../data/') + +async function main() { + createDataDir() + writeReleasesInDataDir(await fetchReleases()) +} + +function createDataDir() { + if (!fs.existsSync(dataDir)) { + fs.mkdirSync(dataDir) + } +} + +async function fetchReleases() { + if (!githubAuthToken) { + return fakeData + } + try { + const response = await fetch('https://api.github.com/graphql', { + method: 'post', + headers: new Headers({ Authorization: `bearer ${githubAuthToken}` }), + body: JSON.stringify({ query }) + }) + const { + data: { + repository: { + releases: { nodes: releases } + } + } + } = await response.json() + return releases.filter(Boolean).reverse() + } catch (e) { + return fakeData + } +} + +function writeReleasesInDataDir(releases) { + // The last release name is fetched on all pages (to display the banner) + // whereas the full release data is used only in the dedicated page, that why + // we deduplicate the releases data in two separated files that can be + // bundled/fetched separately. + fs.writeFileSync( + path.join(dataDir, 'releases.json'), + JSON.stringify(releases) + ) + fs.writeFileSync( + path.join(dataDir, 'last-release.json'), + JSON.stringify({ lastRelease: releases[0].name }) + ) +} + +main() diff --git a/source/scripts/postinstall.js b/source/scripts/postinstall.js index fff6d1566..09e33f836 100644 --- a/source/scripts/postinstall.js +++ b/source/scripts/postinstall.js @@ -1 +1,2 @@ require('./dottednames.js') +require('./fetch-releases.js') diff --git a/source/sites/mon-entreprise.fr/App.css b/source/sites/mon-entreprise.fr/App.css index 7b5b75b2f..e973c688d 100644 --- a/source/sites/mon-entreprise.fr/App.css +++ b/source/sites/mon-entreprise.fr/App.css @@ -13,7 +13,6 @@ body, min-height: 100%; display: flex; flex-direction: column; - overflow: auto; } @media (min-width: 500px) { @@ -22,7 +21,6 @@ body, flex: 1; } .app-container { - overflow: auto; min-height: 100vh; height: auto; } diff --git a/source/sites/mon-entreprise.fr/App.tsx b/source/sites/mon-entreprise.fr/App.tsx index 9d5fa7d64..b87f73082 100644 --- a/source/sites/mon-entreprise.fr/App.tsx +++ b/source/sites/mon-entreprise.fr/App.tsx @@ -1,37 +1,44 @@ -import Route404 from 'Components/Route404'; -import { SitePathsContext } from 'Components/utils/withSitePaths'; -import { rules as baseRulesEn, rulesFr as baseRulesFr } from 'Engine/rules'; -import 'iframe-resizer'; -import createRavenMiddleware from 'raven-for-redux'; -import Raven from 'raven-js'; -import React, { useContext, useEffect } from 'react'; -import { Helmet } from 'react-helmet'; -import { useTranslation } from 'react-i18next'; -import { Route, Switch } from 'react-router-dom'; -import 'Ui/index.css'; -import Provider from '../../Provider'; -import { persistEverything, retrievePersistedState } from '../../storage/persistEverything'; -import { persistSimulation, retrievePersistedSimulation } from '../../storage/persistSimulation'; -import Tracker, { devTracker } from '../../Tracker'; -import { getSessionStorage, inIframe } from '../../utils'; -import './App.css'; -import Footer from './layout/Footer/Footer'; -import Header from './layout/Header'; -import trackSimulatorActions from './middlewares/trackSimulatorActions'; -import Créer from './pages/Créer'; -import Couleur from './pages/Dev/Couleur'; -import IntegrationTest from './pages/Dev/IntegrationTest'; -import Personas from './pages/Dev/Personas'; -import Sitemap from './pages/Dev/Sitemap'; -import Documentation from './pages/Documentation'; -import Gérer from './pages/Gérer'; -import Iframes from './pages/Iframes'; -import Integration from './pages/integration/index'; -import Landing from './pages/Landing/Landing'; -import Simulateurs from './pages/Simulateurs'; -import ÉconomieCollaborative from './pages/ÉconomieCollaborative'; -import redirects from './redirects'; -import { constructLocalizedSitePath } from './sitePaths'; +import Route404 from 'Components/Route404' +import { SitePathsContext } from 'Components/utils/withSitePaths' +import { rules as baseRulesEn, rulesFr as baseRulesFr } from 'Engine/rules' +import 'iframe-resizer' +import createRavenMiddleware from 'raven-for-redux' +import Raven from 'raven-js' +import React, { useContext, useEffect } from 'react' +import { Helmet } from 'react-helmet' +import { useTranslation } from 'react-i18next' +import { Route, Switch } from 'react-router-dom' +import 'Ui/index.css' +import Provider from '../../Provider' +import { + persistEverything, + retrievePersistedState +} from '../../storage/persistEverything' +import { + persistSimulation, + retrievePersistedSimulation +} from '../../storage/persistSimulation' +import Tracker, { devTracker } from '../../Tracker' +import { getSessionStorage, inIframe } from '../../utils' +import './App.css' +import Footer from './layout/Footer/Footer' +import Header from './layout/Header' +import trackSimulatorActions from './middlewares/trackSimulatorActions' +import Créer from './pages/Créer' +import Couleur from './pages/Dev/Couleur' +import IntegrationTest from './pages/Dev/IntegrationTest' +import Personas from './pages/Dev/Personas' +import Sitemap from './pages/Dev/Sitemap' +import Documentation from './pages/Documentation' +import Gérer from './pages/Gérer' +import Iframes from './pages/Iframes' +import Integration from './pages/integration/index' +import Landing from './pages/Landing/Landing' +import Nouveautés from './pages/Nouveautés/Nouveautés' +import Simulateurs from './pages/Simulateurs' +import ÉconomieCollaborative from './pages/ÉconomieCollaborative' +import redirects from './redirects' +import { constructLocalizedSitePath } from './sitePaths' if (process.env.NODE_ENV === 'production') { Raven.config( @@ -70,7 +77,8 @@ function InFranceRoute({ basename, language }) { ...retrievePersistedState(), previousSimulation: retrievePersistedSimulation(), rules - }}> + }} + > ) @@ -113,6 +121,7 @@ const App = () => { component={Documentation} /> + { {' • '} - {' • '} - - Nouveautés - + {i18n.language === 'fr' && ( + <> + {' • '} + Nouveautés + + )} {' • '} Stats - {' • '}{' '} + {' • '} Intégrer nos simulateurs diff --git a/source/sites/mon-entreprise.fr/layout/Header.tsx b/source/sites/mon-entreprise.fr/layout/Header.tsx index fa0c2eb4f..fac62a808 100644 --- a/source/sites/mon-entreprise.fr/layout/Header.tsx +++ b/source/sites/mon-entreprise.fr/layout/Header.tsx @@ -6,47 +6,58 @@ import urssafSvg from 'Images/urssaf.svg' import React, { useContext } from 'react' import { useTranslation } from 'react-i18next' import { Link } from 'react-router-dom' +import NewsBanner from './NewsBanner' export default function Header() { const sitePaths = useContext(SitePathsContext) const { language } = useTranslation().i18n return ( -
- - logo mon-entreprise.fr - -
- + + > + + logo mon-entreprise.fr + + + + ) } diff --git a/source/sites/mon-entreprise.fr/layout/NewsBanner.tsx b/source/sites/mon-entreprise.fr/layout/NewsBanner.tsx new file mode 100644 index 000000000..2c60012dd --- /dev/null +++ b/source/sites/mon-entreprise.fr/layout/NewsBanner.tsx @@ -0,0 +1,39 @@ +import { useLocalStorage, writeStorage } from '@rehooks/local-storage' +import { SitePathsContext } from 'Components/utils/withSitePaths' +import React, { useContext } from 'react' +import emoji from 'react-easy-emoji' +import { useTranslation } from 'react-i18next' +import { Link } from 'react-router-dom' +import { lastRelease } from '../../../data/last-release.json' +import { inIframe } from '../../../utils' + +const localStorageKey = 'last-viewed-release' + +export const hideNewsBanner = () => writeStorage(localStorageKey, lastRelease) + +export default function NewsBanner() { + const [lastViewedRelease] = useLocalStorage(localStorageKey) + const sitePaths = useContext(SitePathsContext) + const { i18n } = useTranslation() + + const showBanner = + lastViewedRelease !== lastRelease && i18n.language === 'fr' && !inIframe() + + // We only want to show the banner to returning visitors, so we initiate the + // local storage value with the last release. + if (showBanner && lastViewedRelease === undefined) { + hideNewsBanner() + } + + return showBanner ? ( +
+ + {emoji('✨')} Découvrez les nouveautés de{' '} + {lastRelease} + + + × + +
+ ) : null +} diff --git a/source/sites/mon-entreprise.fr/pages/Nouveautés/Nouveautés.tsx b/source/sites/mon-entreprise.fr/pages/Nouveautés/Nouveautés.tsx new file mode 100644 index 000000000..4f286930c --- /dev/null +++ b/source/sites/mon-entreprise.fr/pages/Nouveautés/Nouveautés.tsx @@ -0,0 +1,186 @@ +import { Markdown } from 'Components/utils/markdown' +import { SitePathsContext } from 'Components/utils/withSitePaths' +import React, { useContext, useEffect } from 'react' +import emoji from 'react-easy-emoji' +import { Redirect, useHistory, useRouteMatch } from 'react-router' +import { Link, NavLink } from 'react-router-dom' +import styled from 'styled-components' +import useSWR from 'swr' +import { hideNewsBanner } from '../../layout/NewsBanner' + +const fetcher = (url: RequestInfo) => fetch(url).then(r => r.json()) +const slugify = (name: string) => name.toLowerCase().replace(' ', '-') + +export default function Nouveautés() { + // The release.json file may be big, we don't want to include it in the main + // bundle, that's why we only fetch it on this page. Alternatively we could + // use import("data/release.json") and configure code splitting with Webpack. + const { data } = useSWR('/data/releases.json', fetcher) + const history = useHistory() + const sitePaths = useContext(SitePathsContext) + const slug = useRouteMatch<{ slug: string }>(`${sitePaths.nouveautés}/:slug`) + ?.params?.slug + const selectedRelease = data?.findIndex(({ name }) => slugify(name) === slug) + + useEffect(hideNewsBanner, []) + useEffect(() => { + window.scrollTo({ top: 0 }) + }, [selectedRelease]) + + const getPath = (index: number) => + `${sitePaths.nouveautés}/${slugify(data[index].name)}` + + if (!data) { + return null + } else if (!slug || selectedRelease === -1) { + return + } + + return ( + <> +

Les nouveautés {emoji('✨')}

+

+ Nous améliorons le site en continu à partir de vos retours. Découvrez + les{' '} + {selectedRelease === 0 + ? 'dernières nouveautés' + : `nouveautés de ${data[selectedRelease].name.toLowerCase()}`}{' '} + : +

+ { + history.push(getPath(Number(evt.target.value))) + }} + > + {data.map(({ name }, index) => ( + + ))} + + + + {data.map(({ name }, index) => ( +
  • + + {name} + +
  • + ))} +
    + + + + {selectedRelease + 1 < data.length ? ( + + ← {data[selectedRelease + 1].name} + + ) : ( + // For spacing + )} + {selectedRelease > 0 && ( + + {data[selectedRelease - 1].name} → + + )} + + +
    + + ) +} + +const removeGithubIssuesReferences = (text: string) => + text.replace(/#[0-9]{1,5}/g, '') + +const TextRenderer = ({ children }) => ( + <>{emoji(removeGithubIssuesReferences(children))} +) + +const NewsSection = styled.section` + display: flex; + justify-content: space-between; + align-items: flex-start; + + @media (min-width: 1250px) { + margin-left: -175px; + } +` + +const Sidebar = styled.ul` + display: flex; + flex-direction: column; + position: sticky; + top: 20px; + margin-right: 25px; + padding-left: 0; + font-size: 0.9em; + border-right: 1px solid var(--lighterColor); + + @media (max-width: 700px) { + display: none; + } + + li { + list-style-type: none; + list-style-position: inside; + width: 150px; + padding: 0; + margin: 0; + + a { + display: block; + color: inherit; + text-decoration: none; + padding: 4px 10px; + margin: 0; + + &:hover, + &.active { + background: var(--lightestColor); + } + + &.active { + font-weight: bold; + } + } + } +` + +const SmallScreenSelect = styled.select` + display: none; + + @media (max-width: 700px) { + display: initial; + } +` + +const MainBlock = styled.div` + flex: 1; + + > h1:first-child, + h2:first-child, + h3:first-child { + margin-top: 0px; + } +` + +const NavigationButtons = styled.div` + display: flex; + justify-content: space-between; + margin-top: 40px; + + a { + cursor: pointer; + background: var(--lightestColor); + padding: 20px 30px; + } +` diff --git a/source/sites/mon-entreprise.fr/sitePaths.ts b/source/sites/mon-entreprise.fr/sitePaths.ts index 559f341d0..e7920b1d5 100644 --- a/source/sites/mon-entreprise.fr/sitePaths.ts +++ b/source/sites/mon-entreprise.fr/sitePaths.ts @@ -111,6 +111,7 @@ export const constructLocalizedSitePath = (language: string) => { '/votre-situation' ) }, + nouveautés: t('path.nouveautés', '/nouveautés'), documentation: { exemples: t('path.documentation.exemples', '/exemples'), index: t('path.documentation.index', '/documentation') diff --git a/source/types/app-env.d.ts b/source/types/app-env.d.ts index 9208b9579..b3ec2e780 100644 --- a/source/types/app-env.d.ts +++ b/source/types/app-env.d.ts @@ -4,5 +4,6 @@ declare module NodeJS { FR_SITE: string NODE_ENV: 'development' | 'production' MASTER: boolean + GITHUB_API_SECRET: string } } diff --git a/source/webpack.common.js b/source/webpack.common.js index f410cdeda..5ec4c75da 100644 --- a/source/webpack.common.js +++ b/source/webpack.common.js @@ -55,6 +55,10 @@ module.exports.default = { from: './source/images', to: 'images' }, + { + from: './source/data', + to: 'data' + }, { from: './source/sites/mon-entreprise.fr/favicon', to: 'favicon' diff --git a/yarn.lock b/yarn.lock index f2500b205..36ea8fc9e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1056,6 +1056,11 @@ promise-limit "^2.5.0" puppeteer "^1.7.0" +"@rehooks/local-storage@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@rehooks/local-storage/-/local-storage-2.1.1.tgz#e9d9a36a17308667f2aee9b01cbec9a29d53d341" + integrity sha512-w+7kwnFn3tdRreDG+Pi610eqozTyuJnmYIqwa0GojNpoLMjnYDSCF02eyWQOkVPUj1UHf+Zeqnjg+jzqg3OcuQ== + "@sinonjs/commons@^1", "@sinonjs/commons@^1.3.0", "@sinonjs/commons@^1.7.0": version "1.7.0" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.0.tgz#f90ffc52a2e519f018b13b6c4da03cbff36ebed6" @@ -3757,6 +3762,11 @@ domutils@^2.0.0: domelementtype "^2.0.1" domhandler "^3.0.0" +dotenv@=8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.1.0.tgz#d811e178652bfb8a1e593c6dd704ec7e90d85ea2" + integrity sha512-GUE3gqcDCaMltj2++g6bRQ5rBJWtkWTmqmD0fo1RnnMuUqHNCt2oTPeDnS9n6fKYvlhn7AeBkb38lymBtWBQdA== + duplexify@^3.4.2, duplexify@^3.6.0: version "3.7.1" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" @@ -3841,6 +3851,13 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= +encoding@^0.1.11: + version "0.1.12" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= + dependencies: + iconv-lite "~0.4.13" + end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -4401,7 +4418,7 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -fast-deep-equal@^2.0.1: +fast-deep-equal@2.0.1, fast-deep-equal@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= @@ -5294,7 +5311,7 @@ i18next@^18.0.1: dependencies: "@babel/runtime" "^7.3.1" -iconv-lite@0.4.24, iconv-lite@^0.4.24: +iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -5728,7 +5745,7 @@ is-regexp@^1.0.0: resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= -is-stream@^1.1.0: +is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -5821,6 +5838,14 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= +isomorphic-fetch@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + isomorphic-style-loader@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/isomorphic-style-loader/-/isomorphic-style-loader-5.1.0.tgz#4845f90bb9828f3dfecc82d0574c9ed01bbaba2d" @@ -7257,6 +7282,14 @@ no-case@^2.2.0: dependencies: lower-case "^1.1.1" +node-fetch@^1.0.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -10077,6 +10110,13 @@ svgo@^0.7.0: sax "~1.2.1" whet.extend "~0.9.9" +swr@^0.1.16: + version "0.1.16" + resolved "https://registry.yarnpkg.com/swr/-/swr-0.1.16.tgz#e1667f2260ac091fb4c2cc6255ca1b8ff83b0acd" + integrity sha512-E+i0pJOCCPRxnwQMFKf3RYr4os+B4swNuwnHo+pBCNxwfuJiN0ZalwP9u4OqjoeMl9eZFgcQYUPHD8KVyTqM1g== + dependencies: + fast-deep-equal "2.0.1" + symbol-observable@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" @@ -10853,7 +10893,7 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" -whatwg-fetch@^3.0.0: +whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==