mirror of
https://github.com/uetchy/namae.git
synced 2025-03-17 04:30:31 +09:00
feat(web): typescript
This commit is contained in:
parent
b07b597d2e
commit
88a0374594
@ -1 +0,0 @@
|
|||||||
{}
|
|
@ -28,12 +28,17 @@
|
|||||||
"react-scripts": "3.1.1",
|
"react-scripts": "3.1.1",
|
||||||
"react-spinners": "^0.6.1",
|
"react-spinners": "^0.6.1",
|
||||||
"react-tippy": "^1.2.3",
|
"react-tippy": "^1.2.3",
|
||||||
"styled-components": "^4.3.2"
|
"styled-components": "^4.3.2",
|
||||||
|
"typescript": "3.5.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "^4.1.0",
|
"@testing-library/jest-dom": "^4.1.0",
|
||||||
"@testing-library/react": "^9.1.3",
|
"@testing-library/react": "^9.1.3",
|
||||||
|
"@types/i18next-node-fs-backend": "^2.1.0",
|
||||||
"@types/jest": "^24.0.18",
|
"@types/jest": "^24.0.18",
|
||||||
|
"@types/node": "^12.7.3",
|
||||||
|
"@types/react-helmet": "^5.0.9",
|
||||||
|
"@types/styled-components": "^4.1.18",
|
||||||
"i18next-node-fs-backend": "^2.1.3"
|
"i18next-node-fs-backend": "^2.1.3"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
|
@ -34,7 +34,7 @@ export default function App() {
|
|||||||
const [query, setQuery] = useDeferredState(1000, '')
|
const [query, setQuery] = useDeferredState(1000, '')
|
||||||
const [inputValue, setInputValue] = useState('')
|
const [inputValue, setInputValue] = useState('')
|
||||||
const [suggested, setSuggested] = useState(false)
|
const [suggested, setSuggested] = useState(false)
|
||||||
const inputRef = useRef()
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
const {
|
const {
|
||||||
t,
|
t,
|
||||||
i18n: { language },
|
i18n: { language },
|
||||||
@ -54,19 +54,19 @@ export default function App() {
|
|||||||
}, [query])
|
}, [query])
|
||||||
|
|
||||||
// set input value
|
// set input value
|
||||||
function onInputChange(e) {
|
function onInputChange(e: React.FormEvent<HTMLInputElement>) {
|
||||||
const value = e.target.value
|
const value = e.currentTarget.value
|
||||||
setInputValue(value)
|
setInputValue(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear input form and focus on it
|
// clear input form and focus on it
|
||||||
function onLogoClick(e) {
|
function onLogoClick(e: React.MouseEvent<HTMLDivElement>) {
|
||||||
setInputValue('')
|
setInputValue('')
|
||||||
inputRef.current.focus()
|
inputRef.current!.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
// invoke when user clicked one of the suggested items
|
// invoke when user clicked one of the suggested items
|
||||||
function onSuggestionCompleted(name) {
|
function onSuggestionCompleted(name: string) {
|
||||||
setInputValue(name)
|
setInputValue(name)
|
||||||
setSuggested(true)
|
setSuggested(true)
|
||||||
}
|
}
|
@ -16,7 +16,7 @@ const COLORS = {
|
|||||||
error: '#ff388b',
|
error: '#ff388b',
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Card({ title, children }) {
|
export const Card: React.FC<{ title: string }> = ({ title, children }) => {
|
||||||
return (
|
return (
|
||||||
<CardContainer>
|
<CardContainer>
|
||||||
<CardTitle>{title}</CardTitle>
|
<CardTitle>{title}</CardTitle>
|
||||||
@ -36,7 +36,11 @@ export function Card({ title, children }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Repeater({ items = [], moreItems = [], children }) {
|
export const Repeater: React.FC<{
|
||||||
|
items: string[]
|
||||||
|
moreItems?: string[]
|
||||||
|
children: (name: string) => React.ReactNode
|
||||||
|
}> = ({ items = [], moreItems = [], children }) => {
|
||||||
const [revealAlternatives, setRevealAlternatives] = useState(false)
|
const [revealAlternatives, setRevealAlternatives] = useState(false)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
@ -66,7 +70,23 @@ export function Repeater({ items = [], moreItems = [], children }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DedicatedAvailability({
|
interface Response {
|
||||||
|
error?: string
|
||||||
|
availability: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DedicatedAvailability: React.FC<{
|
||||||
|
name: string
|
||||||
|
query?: string
|
||||||
|
message?: string
|
||||||
|
messageIfTaken?: string
|
||||||
|
service: string
|
||||||
|
link: string
|
||||||
|
linkIfTaken?: string
|
||||||
|
prefix?: string
|
||||||
|
suffix?: string
|
||||||
|
icon: React.ReactNode
|
||||||
|
}> = ({
|
||||||
name,
|
name,
|
||||||
query = undefined,
|
query = undefined,
|
||||||
message = '',
|
message = '',
|
||||||
@ -77,10 +97,10 @@ export function DedicatedAvailability({
|
|||||||
prefix = '',
|
prefix = '',
|
||||||
suffix = '',
|
suffix = '',
|
||||||
icon,
|
icon,
|
||||||
}) {
|
}) => {
|
||||||
const response = useFetch(
|
const response = useFetch(
|
||||||
`/availability/${service}/${encodeURIComponent(query || name)}`
|
`/availability/${service}/${encodeURIComponent(query || name)}`
|
||||||
)
|
) as Response
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
throw new Error(`${service}: ${response.error}`)
|
throw new Error(`${service}: ${response.error}`)
|
||||||
@ -99,7 +119,17 @@ export function DedicatedAvailability({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ExistentialAvailability({
|
export const ExistentialAvailability: React.FC<{
|
||||||
|
name: string
|
||||||
|
target: string
|
||||||
|
message?: string
|
||||||
|
messageIfTaken?: string
|
||||||
|
link: string
|
||||||
|
linkIfTaken?: string
|
||||||
|
prefix?: string
|
||||||
|
suffix?: string
|
||||||
|
icon: React.ReactNode
|
||||||
|
}> = ({
|
||||||
name,
|
name,
|
||||||
message = '',
|
message = '',
|
||||||
messageIfTaken = undefined,
|
messageIfTaken = undefined,
|
||||||
@ -109,8 +139,8 @@ export function ExistentialAvailability({
|
|||||||
prefix = '',
|
prefix = '',
|
||||||
suffix = '',
|
suffix = '',
|
||||||
icon,
|
icon,
|
||||||
}) {
|
}) => {
|
||||||
const response = useFetch(target, null, { metadata: true })
|
const response = useFetch(target, undefined, { metadata: true })
|
||||||
|
|
||||||
if (response.status !== 404 && response.status !== 200) {
|
if (response.status !== 404 && response.status !== 200) {
|
||||||
throw new Error(`${name}: ${response.status}`)
|
throw new Error(`${name}: ${response.status}`)
|
||||||
@ -131,7 +161,15 @@ export function ExistentialAvailability({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Result = ({
|
export const Result: React.FC<{
|
||||||
|
title: string
|
||||||
|
message?: string
|
||||||
|
link?: string
|
||||||
|
icon: React.ReactNode
|
||||||
|
color?: string
|
||||||
|
prefix?: string
|
||||||
|
suffix?: string
|
||||||
|
}> = ({
|
||||||
title,
|
title,
|
||||||
message = '',
|
message = '',
|
||||||
link,
|
link,
|
||||||
@ -170,13 +208,16 @@ export const Result = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ErrorBoundary extends React.Component {
|
class ErrorBoundary extends React.Component<
|
||||||
constructor(props) {
|
{},
|
||||||
|
{ hasError: boolean; message: string }
|
||||||
|
> {
|
||||||
|
constructor(props: {}) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = { hasError: false }
|
this.state = { hasError: false, message: '' }
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromError(error) {
|
static getDerivedStateFromError(error: Error) {
|
||||||
return { hasError: true, message: error.message }
|
return { hasError: true, message: error.message }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,7 +245,7 @@ class ErrorBoundary extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const CellError = ({ children }) => (
|
const CellError: React.FC = ({ children }) => (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Suspense
|
<Suspense
|
||||||
fallback={
|
fallback={
|
@ -7,8 +7,10 @@ import { TiArrowSync } from 'react-icons/ti'
|
|||||||
import { capitalize } from '../util/text'
|
import { capitalize } from '../util/text'
|
||||||
import { mobile } from '../util/css'
|
import { mobile } from '../util/css'
|
||||||
|
|
||||||
|
type Modifier = (word: string) => string
|
||||||
|
|
||||||
const maximumCount = 3
|
const maximumCount = 3
|
||||||
const modifiers = [
|
const modifiers: Modifier[] = [
|
||||||
(word) => `${capitalize(word)}ify`,
|
(word) => `${capitalize(word)}ify`,
|
||||||
(word) => `lib${lower(word)}`,
|
(word) => `lib${lower(word)}`,
|
||||||
(word) => `Omni${capitalize(word)}`,
|
(word) => `Omni${capitalize(word)}`,
|
||||||
@ -43,11 +45,11 @@ const modifiers = [
|
|||||||
(word) => `${capitalize(word)}`,
|
(word) => `${capitalize(word)}`,
|
||||||
]
|
]
|
||||||
|
|
||||||
function lower(word) {
|
function lower(word: string) {
|
||||||
return word.toLowerCase()
|
return word.toLowerCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
function shuffleArray(array) {
|
function shuffleArray(array: any[]) {
|
||||||
for (let i = array.length - 1; i > 0; i--) {
|
for (let i = array.length - 1; i > 0; i--) {
|
||||||
const j = Math.floor(Math.random() * (i + 1))
|
const j = Math.floor(Math.random() * (i + 1))
|
||||||
const temp = array[i]
|
const temp = array[i]
|
||||||
@ -57,15 +59,15 @@ function shuffleArray(array) {
|
|||||||
return array
|
return array
|
||||||
}
|
}
|
||||||
|
|
||||||
function sampleFromArray(array, maximum) {
|
function sampleFromArray(array: any[], maximum: number) {
|
||||||
return shuffleArray(array).slice(0, maximum)
|
return shuffleArray(array).slice(0, maximum)
|
||||||
}
|
}
|
||||||
|
|
||||||
function modifyWord(word) {
|
function modifyWord(word: string) {
|
||||||
return modifiers[Math.floor(Math.random() * modifiers.length)](word)
|
return modifiers[Math.floor(Math.random() * modifiers.length)](word)
|
||||||
}
|
}
|
||||||
|
|
||||||
function fillArray(array, filler, maximum) {
|
function fillArray(array: any[], filler: string, maximum: number) {
|
||||||
const deficit = maximum - array.length
|
const deficit = maximum - array.length
|
||||||
if (deficit > 0) {
|
if (deficit > 0) {
|
||||||
array = [...array, ...Array(deficit).fill(filler)]
|
array = [...array, ...Array(deficit).fill(filler)]
|
||||||
@ -73,33 +75,37 @@ function fillArray(array, filler, maximum) {
|
|||||||
return array
|
return array
|
||||||
}
|
}
|
||||||
|
|
||||||
async function findSynonyms(word) {
|
async function findSynonyms(word: string) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&dt=ss&ie=UTF-8&oe=UTF-8&dj=1&q=${encodeURIComponent(
|
`https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&dt=ss&ie=UTF-8&oe=UTF-8&dj=1&q=${encodeURIComponent(
|
||||||
word
|
word
|
||||||
)}`
|
)}`
|
||||||
)
|
)
|
||||||
const json = await response.json()
|
const json: {
|
||||||
const synonyms = [
|
synsets: Array<{ entry: Array<{ synonym: string[] }> }>
|
||||||
...new Set(
|
} = await response.json()
|
||||||
|
const synonyms = Array.from(
|
||||||
|
new Set<string>(
|
||||||
json.synsets.reduce(
|
json.synsets.reduce(
|
||||||
(sum, synset) =>
|
(sum, synset) => [...sum, ...synset.entry.map((e) => e.synonym[0])],
|
||||||
(sum = [...sum, ...synset.entry.map((e) => e.synonym[0])]),
|
[] as string[]
|
||||||
[]
|
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
].filter((word) => !word.match(/[\s-]/))
|
).filter((word) => !word.match(/[\s-]/))
|
||||||
return synonyms
|
return synonyms
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Suggestion({ query, onSubmit }) {
|
const Suggestion: React.FC<{
|
||||||
|
query: string
|
||||||
|
onSubmit: (name: string) => void
|
||||||
|
}> = ({ query, onSubmit }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const synonymRef = useRef([])
|
const synonymRef = useRef<string[]>([])
|
||||||
const [bestWords, setBestWords] = useState([])
|
const [bestWords, setBestWords] = useState<string[]>([])
|
||||||
|
|
||||||
function shuffle() {
|
function shuffle() {
|
||||||
const best = fillArray(
|
const best = fillArray(
|
||||||
@ -110,7 +116,7 @@ export default function Suggestion({ query, onSubmit }) {
|
|||||||
setBestWords(best)
|
setBestWords(best)
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyQuery(name) {
|
function applyQuery(name: string) {
|
||||||
onSubmit(name)
|
onSubmit(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,6 +154,8 @@ export default function Suggestion({ query, onSubmit }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Suggestion
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
display: flex;
|
display: flex;
|
@ -5,12 +5,14 @@ import { FaAppStore, FaInfoCircle } from 'react-icons/fa'
|
|||||||
|
|
||||||
import { Card, Result } from '../Cards'
|
import { Card, Result } from '../Cards'
|
||||||
|
|
||||||
function Search({ query }) {
|
const Search: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const term = encodeURIComponent(query)
|
const term = encodeURIComponent(query)
|
||||||
const response = useFetch(
|
const response = useFetch(
|
||||||
`/availability/appstore/${term}?country=${t('countryCode')}`
|
`/availability/appstore/${term}?country=${t('countryCode')}`
|
||||||
)
|
) as {
|
||||||
|
result: Array<{ name: string; viewURL: string; price: number; id: string }>
|
||||||
|
}
|
||||||
const apps = response.result
|
const apps = response.result
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -32,7 +34,7 @@ function Search({ query }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AppStoreCard({ query }) {
|
const AppStoreCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -41,3 +43,5 @@ export default function AppStoreCard({ query }) {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default AppStoreCard
|
@ -4,7 +4,7 @@ import { DiRust } from 'react-icons/di'
|
|||||||
|
|
||||||
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
||||||
|
|
||||||
export default function CratesioCard({ query }) {
|
const CratesioCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const lowerCase = query.toLowerCase()
|
const lowerCase = query.toLowerCase()
|
||||||
|
|
||||||
@ -26,3 +26,5 @@ export default function CratesioCard({ query }) {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default CratesioCard
|
@ -4,7 +4,7 @@ import { FaMapSigns } from 'react-icons/fa'
|
|||||||
|
|
||||||
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
||||||
|
|
||||||
export default function DomainCard({ query }) {
|
const DomainCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const lowerCase = query.toLowerCase()
|
const lowerCase = query.toLowerCase()
|
||||||
|
|
||||||
@ -33,3 +33,5 @@ export default function DomainCard({ query }) {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default DomainCard
|
@ -4,7 +4,7 @@ import { FaGithub } from 'react-icons/fa'
|
|||||||
|
|
||||||
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
||||||
|
|
||||||
export default function GithubCard({ query }) {
|
const GithubCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const lowerCase = query.toLowerCase()
|
const lowerCase = query.toLowerCase()
|
||||||
|
|
||||||
@ -36,3 +36,5 @@ export default function GithubCard({ query }) {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default GithubCard
|
@ -5,13 +5,21 @@ import { FaGithub, FaInfoCircle } from 'react-icons/fa'
|
|||||||
|
|
||||||
import { Card, Result } from '../Cards'
|
import { Card, Result } from '../Cards'
|
||||||
|
|
||||||
function Search({ query }) {
|
const Search: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const searchQuery = encodeURIComponent(`${query} in:name`)
|
const searchQuery = encodeURIComponent(`${query} in:name`)
|
||||||
const limit = 10
|
const limit = 10
|
||||||
const response = useFetch(
|
const response = useFetch(
|
||||||
`https://api.github.com/search/repositories?q=${searchQuery}&per_page=${limit}`
|
`https://api.github.com/search/repositories?q=${searchQuery}&per_page=${limit}`
|
||||||
)
|
) as {
|
||||||
|
items: Array<{
|
||||||
|
full_name: string
|
||||||
|
description: string
|
||||||
|
stargazers_count: number
|
||||||
|
html_url: string
|
||||||
|
id: string
|
||||||
|
}>
|
||||||
|
}
|
||||||
const repos = response.items
|
const repos = response.items
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -35,7 +43,7 @@ function Search({ query }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function GithubSearchCard({ query }) {
|
const GithubSearchCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -44,3 +52,5 @@ export default function GithubSearchCard({ query }) {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default GithubSearchCard
|
@ -4,7 +4,7 @@ import { DiHeroku } from 'react-icons/di'
|
|||||||
|
|
||||||
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
||||||
|
|
||||||
export default function HerokuCard({ query }) {
|
const HerokuCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const lowerCase = query.toLowerCase()
|
const lowerCase = query.toLowerCase()
|
||||||
|
|
||||||
@ -26,3 +26,5 @@ export default function HerokuCard({ query }) {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default HerokuCard
|
@ -4,7 +4,7 @@ import { IoIosBeer } from 'react-icons/io'
|
|||||||
|
|
||||||
import { Card, Repeater, ExistentialAvailability } from '../Cards'
|
import { Card, Repeater, ExistentialAvailability } from '../Cards'
|
||||||
|
|
||||||
export default function HomebrewCard({ query }) {
|
const HomebrewCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const lowerCase = query.toLowerCase()
|
const lowerCase = query.toLowerCase()
|
||||||
|
|
||||||
@ -40,3 +40,5 @@ export default function HomebrewCard({ query }) {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default HomebrewCard
|
@ -4,7 +4,7 @@ import { FaJsSquare } from 'react-icons/fa'
|
|||||||
|
|
||||||
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
||||||
|
|
||||||
export default function JsOrgCard({ query }) {
|
const JsOrgCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const lowerCase = query.toLowerCase()
|
const lowerCase = query.toLowerCase()
|
||||||
|
|
||||||
@ -28,3 +28,5 @@ export default function JsOrgCard({ query }) {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default JsOrgCard
|
@ -5,7 +5,7 @@ import { DiDebian } from 'react-icons/di'
|
|||||||
|
|
||||||
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
||||||
|
|
||||||
export default function LinuxCard({ query }) {
|
const LinuxCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const lowerCase = query.toLowerCase()
|
const lowerCase = query.toLowerCase()
|
||||||
|
|
||||||
@ -34,3 +34,5 @@ export default function LinuxCard({ query }) {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default LinuxCard
|
@ -4,7 +4,7 @@ import { NowIcon } from '../Icons'
|
|||||||
|
|
||||||
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
||||||
|
|
||||||
export default function NowCard({ query }) {
|
const NowCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const lowerCase = query.toLowerCase()
|
const lowerCase = query.toLowerCase()
|
||||||
|
|
||||||
@ -26,3 +26,5 @@ export default function NowCard({ query }) {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default NowCard
|
@ -4,7 +4,7 @@ import { FaNpm } from 'react-icons/fa'
|
|||||||
|
|
||||||
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
||||||
|
|
||||||
export default function NpmCard({ query }) {
|
const NpmCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const lowerCase = query.toLowerCase()
|
const lowerCase = query.toLowerCase()
|
||||||
|
|
||||||
@ -42,3 +42,5 @@ export default function NpmCard({ query }) {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default NpmCard
|
@ -5,10 +5,12 @@ import { FaBuilding, FaInfoCircle } from 'react-icons/fa'
|
|||||||
|
|
||||||
import { Card, Result } from '../Cards'
|
import { Card, Result } from '../Cards'
|
||||||
|
|
||||||
function Search({ query }) {
|
const Search: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const term = encodeURIComponent(query)
|
const term = encodeURIComponent(query)
|
||||||
const response = useFetch(`/availability/nta/${term}`)
|
const response = useFetch(`/availability/nta/${term}`) as {
|
||||||
|
result: Array<{ name: string; phoneticName: string }>
|
||||||
|
}
|
||||||
const apps = response.result
|
const apps = response.result
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -29,7 +31,7 @@ function Search({ query }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NtaCard({ query }) {
|
const NtaCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -38,3 +40,5 @@ export default function NtaCard({ query }) {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default NtaCard
|
@ -5,7 +5,7 @@ import { FaPython } from 'react-icons/fa'
|
|||||||
import { capitalize } from '../../util/text'
|
import { capitalize } from '../../util/text'
|
||||||
import { Card, DedicatedAvailability, Repeater } from '../Cards'
|
import { Card, DedicatedAvailability, Repeater } from '../Cards'
|
||||||
|
|
||||||
export default function PypiCard({ query }) {
|
const PypiCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const names = [query]
|
const names = [query]
|
||||||
@ -30,3 +30,5 @@ export default function PypiCard({ query }) {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default PypiCard
|
@ -4,7 +4,7 @@ import { FaGem } from 'react-icons/fa'
|
|||||||
|
|
||||||
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
||||||
|
|
||||||
export default function RubyGemsCard({ query }) {
|
const RubyGemsCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const names = [query]
|
const names = [query]
|
||||||
@ -29,3 +29,5 @@ export default function RubyGemsCard({ query }) {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default RubyGemsCard
|
@ -4,7 +4,7 @@ import { FaAws } from 'react-icons/fa'
|
|||||||
|
|
||||||
import { Card, DedicatedAvailability, Repeater } from '../Cards'
|
import { Card, DedicatedAvailability, Repeater } from '../Cards'
|
||||||
|
|
||||||
export default function S3Card({ query }) {
|
const S3Card: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const lowerCase = query.toLowerCase()
|
const lowerCase = query.toLowerCase()
|
||||||
|
|
||||||
@ -30,3 +30,5 @@ export default function S3Card({ query }) {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default S3Card
|
@ -4,7 +4,7 @@ import { FaSlack } from 'react-icons/fa'
|
|||||||
|
|
||||||
import { Card, DedicatedAvailability, Repeater } from '../Cards'
|
import { Card, DedicatedAvailability, Repeater } from '../Cards'
|
||||||
|
|
||||||
export default function SlackCard({ query }) {
|
const SlackCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const lowerCase = query.toLowerCase()
|
const lowerCase = query.toLowerCase()
|
||||||
|
|
||||||
@ -29,3 +29,5 @@ export default function SlackCard({ query }) {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default SlackCard
|
@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
||||||
import { SpectrumIcon } from '../Icons'
|
import { SpectrumIcon } from '../Icons'
|
||||||
|
|
||||||
export default function SpectrumCard({ query }) {
|
const SpectrumCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const names = [query]
|
const names = [query]
|
||||||
|
|
||||||
@ -27,3 +27,5 @@ export default function SpectrumCard({ query }) {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default SpectrumCard
|
@ -5,7 +5,7 @@ import { FaTwitter } from 'react-icons/fa'
|
|||||||
import { capitalize } from '../../util/text'
|
import { capitalize } from '../../util/text'
|
||||||
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
import { Card, Repeater, DedicatedAvailability } from '../Cards'
|
||||||
|
|
||||||
export default function TwitterCard({ query }) {
|
const TwitterCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const lowerCase = query.toLowerCase()
|
const lowerCase = query.toLowerCase()
|
||||||
const capitalCase = capitalize(query)
|
const capitalCase = capitalize(query)
|
||||||
@ -38,3 +38,5 @@ export default function TwitterCard({ query }) {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default TwitterCard
|
@ -21,4 +21,4 @@ if (process.env.NODE_ENV !== 'development') {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceWorker.register()
|
serviceWorker.register({})
|
1
web/src/react-app-env.d.ts
vendored
Normal file
1
web/src/react-app-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="react-scripts" />
|
@ -20,10 +20,18 @@ const isLocalhost = Boolean(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
export function register(config) {
|
type Config = {
|
||||||
|
onSuccess?: (registration: ServiceWorkerRegistration) => void
|
||||||
|
onUpdate?: (registration: ServiceWorkerRegistration) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function register(config?: Config) {
|
||||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||||
// The URL constructor is available in all browsers that support SW.
|
// The URL constructor is available in all browsers that support SW.
|
||||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
|
const publicUrl = new URL(
|
||||||
|
(process as { env: { [key: string]: string } }).env.PUBLIC_URL,
|
||||||
|
window.location.href
|
||||||
|
)
|
||||||
if (publicUrl.origin !== window.location.origin) {
|
if (publicUrl.origin !== window.location.origin) {
|
||||||
// Our service worker won't work if PUBLIC_URL is on a different 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
|
// from what our page is served on. This might happen if a CDN is used to
|
||||||
@ -54,7 +62,7 @@ export function register(config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerValidSW(swUrl, config) {
|
function registerValidSW(swUrl: string, config?: Config) {
|
||||||
navigator.serviceWorker
|
navigator.serviceWorker
|
||||||
.register(swUrl)
|
.register(swUrl)
|
||||||
.then((registration) => {
|
.then((registration) => {
|
||||||
@ -98,7 +106,7 @@ function registerValidSW(swUrl, config) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkValidServiceWorker(swUrl, config) {
|
function checkValidServiceWorker(swUrl: string, config?: Config) {
|
||||||
// Check if the service worker can be found. If it can't reload the page.
|
// Check if the service worker can be found. If it can't reload the page.
|
||||||
fetch(swUrl)
|
fetch(swUrl)
|
||||||
.then((response) => {
|
.then((response) => {
|
1
web/src/types/react-tippy.d.ts
vendored
Normal file
1
web/src/types/react-tippy.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
declare module 'react-tippy'
|
@ -1,6 +1,9 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
|
|
||||||
export function useDeferredState(duration = 1000, initialValue = undefined) {
|
export function useDeferredState<T>(
|
||||||
|
duration = 1000,
|
||||||
|
initialValue: T
|
||||||
|
): [T, React.Dispatch<React.SetStateAction<T>>] {
|
||||||
const [response, setResponse] = useState(initialValue)
|
const [response, setResponse] = useState(initialValue)
|
||||||
const [innerValue, setInnerValue] = useState(initialValue)
|
const [innerValue, setInnerValue] = useState(initialValue)
|
||||||
|
|
@ -1,3 +0,0 @@
|
|||||||
export function isStandalone() {
|
|
||||||
return 'standalone' in window.navigator && window.navigator.standalone
|
|
||||||
}
|
|
8
web/src/util/pwa.ts
Normal file
8
web/src/util/pwa.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
interface CustomNavigator extends Navigator {
|
||||||
|
standalone?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isStandalone() {
|
||||||
|
const navigator: CustomNavigator = window.navigator
|
||||||
|
return 'standalone' in navigator && navigator.standalone
|
||||||
|
}
|
@ -2,7 +2,7 @@ import React, { Suspense } from 'react'
|
|||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import BarLoader from 'react-spinners/BarLoader'
|
import BarLoader from 'react-spinners/BarLoader'
|
||||||
|
|
||||||
export function FullScreenSuspense({ children }) {
|
export const FullScreenSuspense: React.FC = ({ children }) => {
|
||||||
return <Suspense fallback={<Fallback />}>{children}</Suspense>
|
return <Suspense fallback={<Fallback />}>{children}</Suspense>
|
||||||
}
|
}
|
||||||
|
|
@ -1,3 +1,3 @@
|
|||||||
export function capitalize(text) {
|
export function capitalize(text: string) {
|
||||||
return text[0].toUpperCase() + text.slice(1).toLowerCase()
|
return text[0].toUpperCase() + text.slice(1).toLowerCase()
|
||||||
}
|
}
|
25
web/tsconfig.json
Normal file
25
web/tsconfig.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
}
|
1192
web/yarn.lock
1192
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user