diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..27fa079 --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["next/babel", "@emotion/babel-preset-css-prop"], + "plugins": ["@emotion"] +} diff --git a/.gitignore b/.gitignore index ff111da..acbf2a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.next .vscode /build /tmp diff --git a/src/components/Contributors.tsx b/components/Contributors.tsx similarity index 92% rename from src/components/Contributors.tsx rename to components/Contributors.tsx index 4222746..94ff23c 100644 --- a/src/components/Contributors.tsx +++ b/components/Contributors.tsx @@ -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; diff --git a/src/components/Footer.tsx b/components/Footer.tsx similarity index 98% rename from src/components/Footer.tsx rename to components/Footer.tsx index 1ed1826..c35c0a6 100644 --- a/src/components/Footer.tsx +++ b/components/Footer.tsx @@ -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 ( @@ -58,7 +58,7 @@ const Languages = () => {
  • Nederlands
  • -
  • +
  • Bahasa Indonesia
  • diff --git a/src/components/Form.tsx b/components/Form.tsx similarity index 83% rename from src/components/Form.tsx rename to components/Form.tsx index 620394e..a31d52e 100644 --- a/src/components/Form.tsx +++ b/components/Form.tsx @@ -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 ( - + @@ -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, +}; diff --git a/src/components/Icons.tsx b/components/Icons.tsx similarity index 100% rename from src/components/Icons.tsx rename to components/Icons.tsx diff --git a/src/components/Suggestion.tsx b/components/Suggestion.tsx similarity index 98% rename from src/components/Suggestion.tsx rename to components/Suggestion.tsx index 8e0d423..e8b01e9 100644 --- a/src/components/Suggestion.tsx +++ b/components/Suggestion.tsx @@ -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 }, +}; diff --git a/src/components/Welcome.tsx b/components/Welcome.tsx similarity index 90% rename from src/components/Welcome.tsx rename to components/Welcome.tsx index a0c200d..39c220b 100644 --- a/src/components/Welcome.tsx +++ b/components/Welcome.tsx @@ -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 = { @@ -85,15 +86,15 @@ const Welcome: React.FC = () => { - sendGettingStartedEvent()}> + sendGettingStartedEvent()}> {t('gettingStartedWithExample')} or - SpaceX - Netflix - Zoom + SpaceX + Netflix + Zoom diff --git a/src/components/cards/core.tsx b/components/cards/core.tsx similarity index 99% rename from src/components/cards/core.tsx rename to components/cards/core.tsx index 66867df..8b09c4e 100644 --- a/src/components/cards/core.tsx +++ b/components/cards/core.tsx @@ -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'; diff --git a/src/components/cards/index.tsx b/components/cards/index.tsx similarity index 99% rename from src/components/cards/index.tsx rename to components/cards/index.tsx index 5666da8..79359aa 100644 --- a/src/components/cards/index.tsx +++ b/components/cards/index.tsx @@ -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'; diff --git a/src/components/cards/providers/AppStore.tsx b/components/cards/providers/AppStore.tsx similarity index 100% rename from src/components/cards/providers/AppStore.tsx rename to components/cards/providers/AppStore.tsx diff --git a/src/components/cards/providers/ChromeWebStore.tsx b/components/cards/providers/ChromeWebStore.tsx similarity index 100% rename from src/components/cards/providers/ChromeWebStore.tsx rename to components/cards/providers/ChromeWebStore.tsx diff --git a/src/components/cards/providers/Cloudflare.tsx b/components/cards/providers/Cloudflare.tsx similarity index 100% rename from src/components/cards/providers/Cloudflare.tsx rename to components/cards/providers/Cloudflare.tsx diff --git a/src/components/cards/providers/Cratesio.tsx b/components/cards/providers/Cratesio.tsx similarity index 100% rename from src/components/cards/providers/Cratesio.tsx rename to components/cards/providers/Cratesio.tsx diff --git a/src/components/cards/providers/Domains.tsx b/components/cards/providers/Domains.tsx similarity index 100% rename from src/components/cards/providers/Domains.tsx rename to components/cards/providers/Domains.tsx diff --git a/src/components/cards/providers/Firebase.tsx b/components/cards/providers/Firebase.tsx similarity index 100% rename from src/components/cards/providers/Firebase.tsx rename to components/cards/providers/Firebase.tsx diff --git a/src/components/cards/providers/FirefoxAddons.tsx b/components/cards/providers/FirefoxAddons.tsx similarity index 100% rename from src/components/cards/providers/FirefoxAddons.tsx rename to components/cards/providers/FirefoxAddons.tsx diff --git a/src/components/cards/providers/FlyIo.tsx b/components/cards/providers/FlyIo.tsx similarity index 100% rename from src/components/cards/providers/FlyIo.tsx rename to components/cards/providers/FlyIo.tsx diff --git a/src/components/cards/providers/GitHubOrganization.tsx b/components/cards/providers/GitHubOrganization.tsx similarity index 100% rename from src/components/cards/providers/GitHubOrganization.tsx rename to components/cards/providers/GitHubOrganization.tsx diff --git a/src/components/cards/providers/GitHubSearch.tsx b/components/cards/providers/GitHubSearch.tsx similarity index 100% rename from src/components/cards/providers/GitHubSearch.tsx rename to components/cards/providers/GitHubSearch.tsx diff --git a/src/components/cards/providers/GitLab.tsx b/components/cards/providers/GitLab.tsx similarity index 100% rename from src/components/cards/providers/GitLab.tsx rename to components/cards/providers/GitLab.tsx diff --git a/src/components/cards/providers/Heroku.tsx b/components/cards/providers/Heroku.tsx similarity index 100% rename from src/components/cards/providers/Heroku.tsx rename to components/cards/providers/Heroku.tsx diff --git a/src/components/cards/providers/HexPm.tsx b/components/cards/providers/HexPm.tsx similarity index 100% rename from src/components/cards/providers/HexPm.tsx rename to components/cards/providers/HexPm.tsx diff --git a/src/components/cards/providers/Homebrew.tsx b/components/cards/providers/Homebrew.tsx similarity index 100% rename from src/components/cards/providers/Homebrew.tsx rename to components/cards/providers/Homebrew.tsx diff --git a/src/components/cards/providers/Instagram.tsx b/components/cards/providers/Instagram.tsx similarity index 100% rename from src/components/cards/providers/Instagram.tsx rename to components/cards/providers/Instagram.tsx diff --git a/src/components/cards/providers/JsOrg.tsx b/components/cards/providers/JsOrg.tsx similarity index 100% rename from src/components/cards/providers/JsOrg.tsx rename to components/cards/providers/JsOrg.tsx diff --git a/src/components/cards/providers/Linux.tsx b/components/cards/providers/Linux.tsx similarity index 100% rename from src/components/cards/providers/Linux.tsx rename to components/cards/providers/Linux.tsx diff --git a/src/components/cards/providers/ModLand.tsx b/components/cards/providers/ModLand.tsx similarity index 100% rename from src/components/cards/providers/ModLand.tsx rename to components/cards/providers/ModLand.tsx diff --git a/src/components/cards/providers/Netlify.tsx b/components/cards/providers/Netlify.tsx similarity index 100% rename from src/components/cards/providers/Netlify.tsx rename to components/cards/providers/Netlify.tsx diff --git a/src/components/cards/providers/Npm.tsx b/components/cards/providers/Npm.tsx similarity index 100% rename from src/components/cards/providers/Npm.tsx rename to components/cards/providers/Npm.tsx diff --git a/src/components/cards/providers/Nta.tsx b/components/cards/providers/Nta.tsx similarity index 100% rename from src/components/cards/providers/Nta.tsx rename to components/cards/providers/Nta.tsx diff --git a/src/components/cards/providers/Ocaml.tsx b/components/cards/providers/Ocaml.tsx similarity index 100% rename from src/components/cards/providers/Ocaml.tsx rename to components/cards/providers/Ocaml.tsx diff --git a/src/components/cards/providers/PlayStore.tsx b/components/cards/providers/PlayStore.tsx similarity index 100% rename from src/components/cards/providers/PlayStore.tsx rename to components/cards/providers/PlayStore.tsx diff --git a/src/components/cards/providers/ProductHunt.tsx b/components/cards/providers/ProductHunt.tsx similarity index 100% rename from src/components/cards/providers/ProductHunt.tsx rename to components/cards/providers/ProductHunt.tsx diff --git a/src/components/cards/providers/PyPI.tsx b/components/cards/providers/PyPI.tsx similarity index 100% rename from src/components/cards/providers/PyPI.tsx rename to components/cards/providers/PyPI.tsx diff --git a/src/components/cards/providers/RubyGems.tsx b/components/cards/providers/RubyGems.tsx similarity index 100% rename from src/components/cards/providers/RubyGems.tsx rename to components/cards/providers/RubyGems.tsx diff --git a/src/components/cards/providers/S3.tsx b/components/cards/providers/S3.tsx similarity index 100% rename from src/components/cards/providers/S3.tsx rename to components/cards/providers/S3.tsx diff --git a/src/components/cards/providers/Slack.tsx b/components/cards/providers/Slack.tsx similarity index 100% rename from src/components/cards/providers/Slack.tsx rename to components/cards/providers/Slack.tsx diff --git a/src/components/cards/providers/Subreddit.tsx b/components/cards/providers/Subreddit.tsx similarity index 93% rename from src/components/cards/providers/Subreddit.tsx rename to components/cards/providers/Subreddit.tsx index f2b2f67..5595198 100644 --- a/src/components/cards/providers/Subreddit.tsx +++ b/components/cards/providers/Subreddit.tsx @@ -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 ( diff --git a/src/components/cards/providers/Twitter.tsx b/components/cards/providers/Twitter.tsx similarity index 100% rename from src/components/cards/providers/Twitter.tsx rename to components/cards/providers/Twitter.tsx diff --git a/src/components/cards/providers/Vercel.tsx b/components/cards/providers/Vercel.tsx similarity index 100% rename from src/components/cards/providers/Vercel.tsx rename to components/cards/providers/Vercel.tsx diff --git a/src/components/cards/providers/YouTube.tsx b/components/cards/providers/YouTube.tsx similarity index 100% rename from src/components/cards/providers/YouTube.tsx rename to components/cards/providers/YouTube.tsx diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..4f11a03 --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/next-i18next.config.js b/next-i18next.config.js new file mode 100644 index 0000000..d969c7b --- /dev/null +++ b/next-i18next.config.js @@ -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] : [], +}; diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000..e459a15 --- /dev/null +++ b/next.config.js @@ -0,0 +1,11 @@ +const { i18n } = require('./next-i18next.config'); + +/** @type {import('next').NextConfig} */ +module.exports = { + reactStrictMode: false, + concurrentFeatures: true, + images: { + domains: [], + }, + i18n, +}; diff --git a/package.json b/package.json index 30f72aa..72782d7 100644 --- a/package.json +++ b/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 (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", diff --git a/pages/_app.tsx b/pages/_app.tsx new file mode 100644 index 0000000..95b3c70 --- /dev/null +++ b/pages/_app.tsx @@ -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 ( + + + + + ; +