1
0
mirror of https://github.com/uetchy/namae.git synced 2025-03-16 20:20:38 +09:00

dev: initial attempt

This commit is contained in:
uetchy 2022-06-07 13:30:48 +09:00
parent 09f2755410
commit 4ff733e148
63 changed files with 1239 additions and 8090 deletions

4
.babelrc Normal file
View File

@ -0,0 +1,4 @@
{
"presets": ["next/babel", "@emotion/babel-preset-css-prop"],
"plugins": ["@emotion"]
}

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.next
.vscode
/build
/tmp

View File

@ -1,5 +1,5 @@
import React from 'react';
import styled from 'styled-components';
import styled from '@emotion/styled';
import useSWR from 'swr';
import Tooltip from 'rc-tooltip';
@ -72,8 +72,9 @@ const Item = styled.div`
`;
const avatarSize = 32;
const Avatar = styled.img.attrs({ width: avatarSize, height: avatarSize })`
const Avatar = styled.img`
border-radius: ${avatarSize}px;
`;
Avatar.defaultProps = { width: avatarSize, height: avatarSize };
export default Contributors;

View File

@ -2,10 +2,10 @@ import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { FaGithub, FaProductHunt, FaTwitter } from 'react-icons/fa';
import { GoHeart } from 'react-icons/go';
import styled from 'styled-components';
import styled from '@emotion/styled';
import Contributors from '../components/Contributors';
import { Section } from '../theme';
import { tablet } from '../util/css';
import { Section } from '../src/theme';
import { tablet } from '../src/util/css';
const Footer: React.FC = () => {
return (

View File

@ -1,20 +1,21 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
import React, { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Link, useNavigate } from 'react-router-dom';
import styled from 'styled-components';
import styled from '@emotion/styled';
import { useStoreActions } from '../store';
import { sendQueryEvent } from '../util/analytics';
import { mobile } from '../util/css';
import { useDeferredState } from '../util/hooks';
import { sanitize } from '../util/text';
import { sendQueryEvent } from '../src/util/analytics';
import { mobile } from '../src/util/css';
import { useDeferredState } from '../src/util/hooks';
import { sanitize } from '../src/util/text';
import Suggestion from './Suggestion';
const Form: React.FC<{
initialValue?: string;
useSuggestion?: boolean;
}> = ({ initialValue = '', useSuggestion = true }) => {
const router = useRouter();
const reset = useStoreActions((actions) => actions.stats.reset);
const navigate = useNavigate();
const [inputValue, setInputValue] = useState(initialValue);
const [suggestionQuery, setSuggestionQuery] = useDeferredState(800, '');
const [suggested, setSuggested] = useState(false);
@ -24,7 +25,7 @@ const Form: React.FC<{
function search(query: string) {
reset();
sendQueryEvent(sanitize(query));
navigate(`/s/${query}`);
router.push(`/s/${query}`);
}
// set input value
@ -57,7 +58,7 @@ const Form: React.FC<{
return (
<InputContainer>
<InputHeader>
<Logo to="/">
<Logo href="/">
<LogoImage src="/logo.svg" />
</Logo>
</InputHeader>
@ -120,15 +121,7 @@ const LogoImage = styled.img`
}
`;
const InputView = styled.input.attrs({
type: 'search',
enterkeyhint: 'search',
autocomplete: 'off',
autocorrect: 'off',
autocapitalize: 'off',
spellcheck: 'false',
autoFocus: true,
})`
const InputView = styled.input`
width: 100%;
border: none;
outline: none;
@ -146,3 +139,12 @@ const InputView = styled.input.attrs({
color: #c8cdda;
}
`;
InputView.defaultProps = {
type: 'search',
enterKeyHint: 'search',
autoComplete: 'off',
autoCorrect: 'off',
autoCapitalize: 'off',
spellCheck: 'false',
autoFocus: true,
};

View File

@ -4,13 +4,13 @@ import React, { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { TiArrowSync } from 'react-icons/ti';
import { PropagateLoader } from 'react-spinners';
import styled from 'styled-components';
import styled from '@emotion/styled';
import {
sendAcceptSuggestionEvent,
sendShuffleSuggestionEvent,
} from '../util/analytics';
import { sample, sampleMany, times } from '../util/array';
import { mobile, slideUp } from '../util/css';
} from '../src/util/analytics';
import { sample, sampleMany, times } from '../src/util/array';
import { mobile, slideUp } from '../src/util/css';
import {
capitalize,
germanify,
@ -19,7 +19,7 @@ import {
sanitize,
stem,
upper,
} from '../util/text';
} from '../src/util/text';
type Modifier = (word: string) => string;
@ -345,10 +345,7 @@ const Item = styled.div<{ delay: number }>`
}
`;
const Button = styled(motion.div).attrs({
whileHover: { scale: 1.1 },
whileTap: { scale: 0.9 },
})`
const Button = styled(motion.div)`
margin: 15px 0 0 0;
padding: 8px 12px;
display: flex;
@ -370,3 +367,7 @@ const Button = styled(motion.div).attrs({
background: #a17ff5;
}
`;
Button.defaultProps = {
whileHover: { scale: 1.1 },
whileTap: { scale: 0.9 },
};

View File

@ -1,40 +1,41 @@
import Link from 'next/link';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { DiHeroku } from 'react-icons/di';
import {
FaAws,
FaCloudflare,
FaFirefoxBrowser,
FaFly,
FaGithub,
FaGithubAlt,
FaGitlab,
// FaInstagram,
FaJsSquare,
FaProductHunt,
FaPython,
FaReddit,
FaSlack,
FaTwitter,
FaCloudflare,
FaFirefoxBrowser,
FaYoutube,
FaProductHunt,
FaFly,
} from 'react-icons/fa';
import { IoIosBeer, IoMdAppstore } from 'react-icons/io';
import { IoIosBeer } from 'react-icons/io';
import { MdDomain } from 'react-icons/md';
import { RiBuilding2Fill, RiChromeFill, RiNpmjsFill } from 'react-icons/ri';
import { SiDeno, SiElixir } from 'react-icons/si';
import {
SiAppstore,
SiArchlinux,
SiDebian,
SiDeno,
SiElixir,
SiFirebase,
SiRubygems,
SiRust,
SiUbuntu,
} from 'react-icons/si';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { sendGettingStartedEvent } from '../util/analytics';
import { mobile } from '../util/css';
import styled from '@emotion/styled';
import { sendGettingStartedEvent } from '../src/util/analytics';
import { mobile } from '../src/util/css';
import { NetlifyIcon, NowIcon, OcamlIcon } from './Icons';
const supportedProviders: Record<string, React.ReactNode> = {
@ -85,15 +86,15 @@ const Welcome: React.FC = () => {
<ButtonContainer>
<List>
<ListButton>
<Link to="/s/namae" onClick={() => sendGettingStartedEvent()}>
<Link href="/s/namae" onClick={() => sendGettingStartedEvent()}>
{t('gettingStartedWithExample')}
</Link>
</ListButton>
<Subtle>or</Subtle>
<HList>
<Link to="/s/SpaceX">SpaceX</Link>
<Link to="/s/Netflix">Netflix</Link>
<Link to="/s/Zoom">Zoom</Link>
<Link href="/s/SpaceX">SpaceX</Link>
<Link href="/s/Netflix">Netflix</Link>
<Link href="/s/Zoom">Zoom</Link>
</HList>
</List>
</ButtonContainer>

View File

@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
import { GoInfo } from 'react-icons/go';
import { IoIosFlash } from 'react-icons/io';
import BarLoader from 'react-spinners/BarLoader';
import styled from 'styled-components';
import styled from '@emotion/styled';
import { useStoreActions } from '../../store';
import { sendError, sendExpandEvent } from '../../util/analytics';
import { mobile } from '../../util/css';

View File

@ -1,6 +1,6 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
import styled from '@emotion/styled';
import { mobile } from '../../util/css';
import AppStoreCard from './providers/AppStore';
import ChromeWebStoreCard from './providers/ChromeWebStore';

View File

@ -13,10 +13,7 @@ const SubredditCard: React.FC<{ query: string }> = ({ query }) => {
const lowerCase = normalizedQuery.toLowerCase();
const names = [normalizedQuery];
const moreNames = [
`get${lowerCase}`,
`${lowerCase}_team`,
];
const moreNames = [`get${lowerCase}`, `${lowerCase}_team`];
return (
<Card title={t('providers.reddit')}>

5
next-env.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

31
next-i18next.config.js Normal file
View File

@ -0,0 +1,31 @@
const HttpBackend = require('i18next-http-backend/cjs');
const ChainedBackend = require('i18next-chained-backend').default;
const LocalStorageBackend = require('i18next-localstorage-backend').default;
module.exports = {
backend: {
backendOptions: [{ expirationTime: 60 * 60 * 1000 }, {}], // 1 hour
backends:
typeof window !== 'undefined' ? [LocalStorageBackend, HttpBackend] : [],
},
i18n: {
defaultNS: 'translation',
defaultLocale: 'en',
locales: [
'en',
'ja',
'de',
'fr',
'zh-Hans',
'zh-Hant',
'pt-BR',
'es',
'it',
'ru',
'nl',
'id',
],
},
serializeConfig: false,
use: typeof window !== 'undefined' ? [ChainedBackend] : [],
};

11
next.config.js Normal file
View File

@ -0,0 +1,11 @@
const { i18n } = require('./next-i18next.config');
/** @type {import('next').NextConfig} */
module.exports = {
reactStrictMode: false,
concurrentFeatures: true,
images: {
domains: [],
},
i18n,
};

View File

@ -3,43 +3,48 @@
"description": "namae saves your time searching around registries and checking if the desired name is ready for use.",
"author": "Yasuaki Uechi <y@uechi.io> (https://uechi.io/)",
"scripts": {
"build": "NODE_ENV=production react-scripts build",
"dev": "BROWSER=none react-scripts start",
"eject": "react-scripts eject",
"build": "next build",
"dev": "next dev",
"prepare": "husky install",
"readme": "mdmod README.md --define.owner uetchy",
"start": "next start",
"test": "tsc --noEmit && jest --coverage && CI=true react-scripts test --coverage"
},
"dependencies": {
"@sentry/browser": "^6.19.2",
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@sentry/browser": "^7.0.0",
"cross-fetch": "^3.1.5",
"easy-peasy": "^5.0.4",
"fetch-suspense": "^1.2.2",
"framer-motion": "^6.2.8",
"i18next": ">=21.6.14",
"framer-motion": "^6.3.10",
"i18next": ">=21.8.8",
"i18next-browser-languagedetector": "^6.1.4",
"i18next-chained-backend": "^3.0.2",
"i18next-http-backend": "^1.4.1",
"i18next-localstorage-backend": "^3.1.3",
"i18next-xhr-backend": "^3.2.2",
"mersennetwister": "^0.2.3",
"npm-name": "6.0.1",
"next": "^12.1.6",
"next-i18next": "^11.0.0",
"npm-name": "7.0.1",
"rc-tooltip": "^5.1.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-helmet": "^6.0.0",
"react-i18next": "11.16.1",
"react-icons": "^4.3.1",
"react-router": "^6.2.2",
"react-router-dom": "^6.2.2",
"react-scripts": "5.0.0",
"react-spinners": "^0.11.0",
"react-toastify": "^8.2.0",
"styled-components": "^5.3.5",
"swr": "^1.2.2",
"react-i18next": "11.17.0",
"react-icons": "^4.4.0",
"react-router": "^6.3.0",
"react-spinners": "^0.12.0",
"react-toastify": "^9.0.3",
"swr": "^1.3.0",
"validator": "^13.7.0",
"whoiser": "^1.13.1"
},
"devDependencies": {
"@babel/core": "^7.18.2",
"@emotion/babel-plugin": "^11.9.2",
"@emotion/babel-preset-css-prop": "^11.2.0",
"@sentry/cli": "^1.74.2",
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^12.1.4",
@ -49,8 +54,6 @@
"@types/node": "^16.11.7",
"@types/react-dom": "^17.0.14",
"@types/react-helmet": "^6.1.5",
"@types/react-router-dom": "^5.3.3",
"@types/styled-components": "^5.1.24",
"@types/validator": "^13.7.1",
"@vercel/build-utils": "^2.15.0",
"@vercel/node": "^1.14.0",

40
pages/_app.tsx Normal file
View File

@ -0,0 +1,40 @@
// https://nextjs.org/docs/advanced-features/custom-app
import { Global } from '@emotion/react';
import { StoreProvider } from 'easy-peasy';
import { appWithTranslation } from 'next-i18next';
import { useEffect } from 'react';
import { ToastContainer } from 'react-toastify';
import Footer from '../components/Footer';
import { globalStyle } from '../src/theme';
import { initSentry } from '../src/util/analytics';
import { initCrisp } from '../src/util/crisp';
import { useOpenSearch } from '../src/util/hooks';
import { FullScreenSuspense } from '../src/util/suspense';
import { store } from '../store';
import nextI18nConfig from '../next-i18next.config';
function MyApp({ Component, pageProps }) {
useEffect(() => {
// Client-side-only code
// TODO: https://docs.sentry.io/platforms/javascript/guides/nextjs/
initSentry();
initCrisp();
}, []);
const OpenSearch = useOpenSearch('/opensearch.xml');
return (
<StoreProvider store={store}>
<FullScreenSuspense>
<Global styles={globalStyle} />
<OpenSearch />
<Component {...pageProps} />;
<Footer />
</FullScreenSuspense>
<ToastContainer />
</StoreProvider>
);
}
export default appWithTranslation(MyApp, nextI18nConfig);

66
pages/_document.tsx Normal file
View File

@ -0,0 +1,66 @@
// https://nextjs.org/docs/advanced-features/custom-document
import { Head, Html, Main, NextScript } from 'next/document';
import i18nextConfig from '../next-i18next.config';
export default function Document(props) {
const currentLocale =
props.__NEXT_DATA__.locale || i18nextConfig.i18n.defaultLocale;
return (
<Html lang={currentLocale}>
<Head>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="namae" />
<meta name="msapplication-TileColor" content="#5180fc" />
<meta name="theme-color" content="#632bec" />
<link rel="shortcut icon" href="/favicon.ico" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5180fc" />
<link rel="manifest" href="/manifest.json" />
<meta property="og:title" content="namae — name new project" />
<meta
property="og:description"
content="Check availability of your new app name for major registries at once."
/>
<meta property="og:type" content="website" />
<meta property="og:image" content="https://namae.dev/social.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:creator" content="@uechz" />
<meta name="twitter:image" content="https://namae.dev/social.png" />
<link
href="https://fonts.googleapis.com/css?family=Montserrat:600&display=swap"
rel="stylesheet"
/>
<script
async
defer
data-website-id="a0bfb495-787b-4960-938d-c4a190aa7455"
src="https://analytics.uechi.io/umami.js"
></script>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}

View File

@ -1,11 +1,11 @@
import React from 'react';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import { Content, Header } from '../theme';
import Form from '../components/Form';
import Welcome from '../components/Welcome';
import { Content, Header } from '../src/theme';
export default function Home() {
export default function App() {
const { t } = useTranslation();
return (

View File

@ -1,27 +1,28 @@
import { useRouter } from 'next/router';
import Tooltip from 'rc-tooltip';
import React from 'react';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import { IoIosFlash, IoIosRocket } from 'react-icons/io';
import { useParams } from 'react-router-dom';
import styled from 'styled-components';
import Cards from '../components/cards';
import styled from '@emotion/styled';
import Cards from '../../components/cards';
import {
AvailableIcon,
COLORS as ResultColor,
ResultIcon,
ResultItem,
ResultName,
} from '../components/cards/core';
import Form from '../components/Form';
import { useStoreState } from '../store';
import { Content, Header } from '../theme';
import { mobile } from '../util/css';
import { sanitize } from '../util/text';
} from '../../components/cards/core';
import Form from '../../components/Form';
import { useStoreState } from '../../store';
import { Content, Header } from '../../src/theme';
import { mobile } from '../../src/util/css';
import { sanitize } from '../../src/util/text';
export default function Search() {
const { query } = useParams<{ query: string }>();
const currentQuery = sanitize(query ?? '');
const router = useRouter();
const { query } = router.query;
const currentQuery = sanitize((query as string) ?? '');
const { t } = useTranslation();
return (

View File

@ -1,69 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="title" content="namae" />
<meta
name="description"
content="Check availability of your new app name for major registries at once."
/>
<meta name="keywords" content="name" />
<meta name="robots" content="index, follow" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="namae" />
<meta name="msapplication-TileColor" content="#5180fc" />
<meta name="theme-color" content="#632bec" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="%PUBLIC_URL%/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="%PUBLIC_URL%/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="%PUBLIC_URL%/favicon-16x16.png"
/>
<link
rel="mask-icon"
href="%PUBLIC_URL%/safari-pinned-tab.svg"
color="#5180fc"
/>
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<meta property="og:title" content="namae — name new project" />
<meta
property="og:description"
content="Check availability of your new app name for major registries at once."
/>
<meta property="og:type" content="website" />
<meta property="og:image" content="https://namae.dev/social.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:creator" content="@uechz" />
<meta name="twitter:image" content="https://namae.dev/social.png" />
<link
href="https://fonts.googleapis.com/css?family=Montserrat:600&display=swap"
rel="stylesheet"
/>
<script
async
defer
data-website-id="a0bfb495-787b-4960-938d-c4a190aa7455"
src="https://analytics.uechi.io/umami.js"
></script>
<title>namae</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

View File

@ -1,26 +0,0 @@
import React from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import Footer from './components/Footer';
import Home from './pages/Home';
import Search from './pages/Search';
import { GlobalStyle } from './theme';
import { useOpenSearch } from './util/hooks';
export default function App() {
const OpenSearch = useOpenSearch('/opensearch.xml');
return (
<>
<GlobalStyle />
<OpenSearch />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/s/:query" element={<Search />} />
<Route path="*" element={<Navigate to="/" />} />
</Routes>
<Footer />
</>
);
}

View File

@ -1,49 +0,0 @@
import { StoreProvider } from 'easy-peasy';
import 'rc-tooltip/assets/bootstrap.css';
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { toast, ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { store } from './store';
import { initSentry } from './util/analytics';
import { initCrisp } from './util/crisp';
import './util/i18n';
import { FullScreenSuspense } from './util/suspense';
initSentry();
initCrisp();
ReactDOM.render(
<StoreProvider store={store}>
<FullScreenSuspense>
<BrowserRouter>
<App />
</BrowserRouter>
</FullScreenSuspense>
<ToastContainer />
</StoreProvider>,
document.getElementById('root')
);
serviceWorker.register({
onUpdate: (registration) => {
console.log('Update available');
toast.success('New version available! Click here to update.', {
onClose: () => {
window.location.reload();
},
position: 'top-right',
autoClose: false,
closeButton: false,
closeOnClick: true,
});
if (registration && registration.waiting) {
registration.waiting.postMessage({ type: 'SKIP_WAITING' });
}
},
});

View File

@ -1 +0,0 @@
/// <reference types="react-scripts" />

View File

@ -1,148 +0,0 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
// VERSION = '1.0';
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/.exec(
window.location.hostname
)
);
type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void;
onUpdate?: (registration: ServiceWorkerRegistration) => void;
};
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch((error) => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.then((response) => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && !contentType.includes('javascript'))
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then((registration) => {
registration.unregister();
})
.catch((error) => {
console.error(error.message);
});
}
}

View File

@ -1,30 +1,31 @@
import styled, { createGlobalStyle } from 'styled-components';
import styled, { createGlobalStyle } from '@emotion/styled';
import { mobile } from '../util/css';
import { css } from '@emotion/react';
export const GlobalStyle = createGlobalStyle`
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: 100%;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
line-height: 1.625em;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background: #ffffff;
${mobile} {
background: #f5f5f5;
export const globalStyle = css`
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: 100%;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
'Helvetica Neue', sans-serif;
line-height: 1.625em;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background: #ffffff;
${mobile} {
background: #f5f5f5;
}
}
}
`;
export const Content = styled.div`

View File

@ -46,6 +46,7 @@ export function sendShuffleSuggestionEvent(): void {
}
export function initSentry(): void {
console.log('initSentry');
if (isProduction) {
Sentry.init({
dsn: 'https://7ab2df74aead499b950ebef190cc40b7@sentry.io/1759299',

View File

@ -1,4 +1,4 @@
import { keyframes } from 'styled-components';
import { keyframes } from '@emotion/react';
export const mobile = '@media screen and (max-width: 800px)';
export const tablet = '@media screen and (max-width: 1200px)';

View File

@ -1,5 +1,5 @@
import React, { Suspense } from 'react';
import styled from 'styled-components';
import styled from '@emotion/styled';
import BarLoader from 'react-spinners/BarLoader';
export const FullScreenSuspense: React.FC = ({ children }) => {

View File

@ -1,22 +1,20 @@
{
"compilerOptions": {
"target": "es2015",
"lib": ["dom", "dom.iterable", "ESNext"],
"module": "ESNext",
"moduleResolution": "node",
"resolveJsonModule": true,
"skipLibCheck": true,
"jsx": "react-jsx",
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"noEmit": true,
"downlevelIteration": true,
"noFallthroughCasesInSwitch": true
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
},
"include": ["api", "src", "types"]
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

8620
yarn.lock

File diff suppressed because it is too large Load Diff