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:
parent
09f2755410
commit
4ff733e148
4
.babelrc
Normal file
4
.babelrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"presets": ["next/babel", "@emotion/babel-preset-css-prop"],
|
||||
"plugins": ["@emotion"]
|
||||
}
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
.next
|
||||
.vscode
|
||||
/build
|
||||
/tmp
|
||||
|
@ -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;
|
@ -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 (
|
@ -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,
|
||||
};
|
@ -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 },
|
||||
};
|
@ -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>
|
@ -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';
|
@ -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';
|
@ -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
5
next-env.d.ts
vendored
Normal 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
31
next-i18next.config.js
Normal 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
11
next.config.js
Normal file
@ -0,0 +1,11 @@
|
||||
const { i18n } = require('./next-i18next.config');
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
module.exports = {
|
||||
reactStrictMode: false,
|
||||
concurrentFeatures: true,
|
||||
images: {
|
||||
domains: [],
|
||||
},
|
||||
i18n,
|
||||
};
|
43
package.json
43
package.json
@ -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
40
pages/_app.tsx
Normal 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
66
pages/_document.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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 (
|
@ -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 (
|
@ -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>
|
26
src/App.tsx
26
src/App.tsx
@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
@ -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' });
|
||||
}
|
||||
},
|
||||
});
|
1
src/react-app-env.d.ts
vendored
1
src/react-app-env.d.ts
vendored
@ -1 +0,0 @@
|
||||
/// <reference types="react-scripts" />
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
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`
|
||||
export const globalStyle = css`
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
@ -13,9 +14,9 @@ html {
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
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;
|
||||
|
@ -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',
|
||||
|
@ -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)';
|
||||
|
@ -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 }) => {
|
||||
|
@ -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"]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user