diff --git a/app/components/Battle.js b/app/components/Battle.js
deleted file mode 100644
index 6576e8f..0000000
--- a/app/components/Battle.js
+++ /dev/null
@@ -1,170 +0,0 @@
-import React from 'react'
-import { FaUserFriends, FaFighterJet, FaTrophy, FaTimesCircle } from 'react-icons/fa'
-import PropTypes from 'prop-types'
-import Results from './Results'
-import ThemeContext from '../contexts/theme'
-import { Link } from 'react-router-dom'
-
-function Instructions () {
- const theme = React.useContext(ThemeContext)
-
- return (
-
-
- Instructions
-
-
- -
-
Enter two Github users
-
-
- -
-
Battle
-
-
- -
-
See the winners
-
-
-
-
- )
-}
-
-function PlayerInput ({ onSubmit, label }) {
- const [username, setUsername] = React.useState('')
-
- const handleSubmit = (e) => {
- e.preventDefault()
-
- onSubmit(username)
- }
-
- const handleChange = (event) => setUsername(event.target.value)
- const theme = React.useContext(ThemeContext)
-
- return (
-
- )
-}
-
-PlayerInput.propTypes = {
- onSubmit: PropTypes.func.isRequired,
- label: PropTypes.string.isRequired
-}
-
-function PlayerPreview ({ username, onReset, label }) {
- const theme = React.useContext(ThemeContext)
-
- return (
-
- )
-}
-
-PlayerPreview.propTypes = {
- username: PropTypes.string.isRequired,
- onReset: PropTypes.func.isRequired,
- label: PropTypes.string.isRequired
-}
-
-export default function Battle () {
- const [playerOne, setPlayerOne] = React.useState(null)
- const [playerTwo, setPlayerTwo] = React.useState(null)
-
- const handleSubmit = (id, player) => id === 'playerOne'
- ? setPlayerOne(player)
- : setPlayerTwo(player)
-
- const handleReset = (id) => id === 'playerOne'
- ? setPlayerOne(null)
- : setPlayerTwo(null)
-
- return (
-
-
-
-
-
Players
-
- {playerOne === null
- ?
handleSubmit('playerOne', player)}
- />
- : handleReset('playerOne')}
- />
- }
-
- {playerTwo === null
- ? handleSubmit('playerTwo', player)}
- />
- : handleReset('playerTwo')}
- />
- }
-
-
-
- {playerOne && playerTwo && (
-
- Battle
-
- )}
-
-
- )
-}
\ No newline at end of file
diff --git a/app/components/Battle.tsx b/app/components/Battle.tsx
new file mode 100644
index 0000000..3cdef93
--- /dev/null
+++ b/app/components/Battle.tsx
@@ -0,0 +1,193 @@
+import React, { ChangeEvent, FormEvent } from "react";
+import {
+ FaUserFriends,
+ FaFighterJet,
+ FaTrophy,
+ FaTimesCircle,
+} from "react-icons/fa";
+import PropTypes from "prop-types";
+import Results from "./Results";
+import ThemeContext from "../contexts/theme";
+import { Link } from "react-router-dom";
+
+function Instructions() {
+ const theme = React.useContext(ThemeContext);
+
+ return (
+
+
Instructions
+
+ -
+
Enter two Github users
+
+
+ -
+
Battle
+
+
+ -
+
See the winners
+
+
+
+
+ );
+}
+
+function PlayerInput({
+ onSubmit,
+ label,
+}: {
+ onSubmit: (username: string) => void;
+ label: string;
+}) {
+ const [username, setUsername] = React.useState("");
+
+ const handleSubmit = (e: FormEvent) => {
+ e.preventDefault();
+
+ onSubmit(username);
+ };
+
+ const handleChange = (event: ChangeEvent) =>
+ setUsername(event.target.value);
+ const theme = React.useContext(ThemeContext);
+
+ return (
+
+ );
+}
+
+PlayerInput.propTypes = {
+ onSubmit: PropTypes.func.isRequired,
+ label: PropTypes.string.isRequired,
+};
+
+function PlayerPreview({
+ username,
+ onReset,
+ label,
+}: {
+ label: string;
+ username: string;
+ onReset: () => void;
+}) {
+ const theme = React.useContext(ThemeContext);
+
+ return (
+
+ );
+}
+
+PlayerPreview.propTypes = {
+ username: PropTypes.string.isRequired,
+ onReset: PropTypes.func.isRequired,
+ label: PropTypes.string.isRequired,
+};
+
+export default function Battle() {
+ const [playerOne, setPlayerOne] = React.useState(null);
+ const [playerTwo, setPlayerTwo] = React.useState(null);
+
+ const handleSubmit = (id: string, player: string) =>
+ id === "playerOne" ? setPlayerOne(player) : setPlayerTwo(player);
+
+ const handleReset = (id: string) =>
+ id === "playerOne" ? setPlayerOne(null) : setPlayerTwo(null);
+
+ return (
+
+
+
+
+
Players
+
+ {playerOne === null ? (
+
handleSubmit("playerOne", player)}
+ />
+ ) : (
+ handleReset("playerOne")}
+ />
+ )}
+
+ {playerTwo === null ? (
+ handleSubmit("playerTwo", player)}
+ />
+ ) : (
+ handleReset("playerTwo")}
+ />
+ )}
+
+
+ {playerOne && playerTwo && (
+
+ Battle
+
+ )}
+
+
+ );
+}
diff --git a/app/components/Tooltip.js b/app/components/Tooltip.js
deleted file mode 100644
index 09fce1e..0000000
--- a/app/components/Tooltip.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import useHover from '../hooks/useHover'
-
-const styles = {
- container: {
- position: 'relative',
- display: 'flex'
- },
- tooltip: {
- boxSizing: 'border-box',
- position: 'absolute',
- width: '160px',
- bottom: '100%',
- left: '50%',
- marginLeft: '-80px',
- borderRadius: '3px',
- backgroundColor: 'hsla(0, 0%, 20%, 0.9)',
- padding: '7px',
- marginBottom: '5px',
- color: '#fff',
- textAlign: 'center',
- fontSize: '14px',
- }
-}
-
-export default function Tooltip ({ text, children }) {
- const [hovering, attrs] = useHover()
-
- return (
-
- {hovering === true &&
{text}
}
- {children}
-
- )
-}
-
-Tooltip.propTypes = {
- text: PropTypes.string.isRequired,
-}
\ No newline at end of file
diff --git a/app/components/Tooltip.tsx b/app/components/Tooltip.tsx
new file mode 100644
index 0000000..10523f7
--- /dev/null
+++ b/app/components/Tooltip.tsx
@@ -0,0 +1,46 @@
+import React, { CSSProperties, ReactNode } from "react";
+import PropTypes from "prop-types";
+import useHover from "../hooks/useHover";
+
+const styles = {
+ container: {
+ position: "relative",
+ display: "flex",
+ } as CSSProperties,
+ tooltip: {
+ boxSizing: "border-box",
+ position: "absolute",
+ width: "160px",
+ bottom: "100%",
+ left: "50%",
+ marginLeft: "-80px",
+ borderRadius: "3px",
+ backgroundColor: "hsla(0, 0%, 20%, 0.9)",
+ padding: "7px",
+ marginBottom: "5px",
+ color: "#fff",
+ textAlign: "center",
+ fontSize: "14px",
+ } as CSSProperties,
+};
+
+export default function Tooltip({
+ text,
+ children,
+}: {
+ text: ReactNode;
+ children: ReactNode;
+}) {
+ const [hovering, attrs] = useHover();
+
+ return (
+
+ {hovering === true &&
{text}
}
+ {children}
+
+ );
+}
+
+Tooltip.propTypes = {
+ text: PropTypes.string.isRequired,
+};
diff --git a/app/contexts/theme.js b/app/contexts/theme.js
deleted file mode 100644
index 9373368..0000000
--- a/app/contexts/theme.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import React from 'react'
-
-const ThemeContext = React.createContext()
-
-export default ThemeContext
-export const ThemeConsumer = ThemeContext.Consumer
-export const ThemeProvider = ThemeContext.Provider
diff --git a/app/contexts/theme.ts b/app/contexts/theme.ts
new file mode 100644
index 0000000..5f3a2b9
--- /dev/null
+++ b/app/contexts/theme.ts
@@ -0,0 +1,7 @@
+import React from "react";
+
+const ThemeContext = React.createContext("light");
+
+export default ThemeContext;
+export const ThemeConsumer = ThemeContext.Consumer;
+export const ThemeProvider = ThemeContext.Provider;
diff --git a/app/hooks/useHover.js b/app/hooks/useHover.ts
similarity index 100%
rename from app/hooks/useHover.js
rename to app/hooks/useHover.ts
diff --git a/app/utils/api.js b/app/utils/api.js
deleted file mode 100644
index 080865b..0000000
--- a/app/utils/api.js
+++ /dev/null
@@ -1,78 +0,0 @@
-const id = "YOUR_CLIENT_ID"
-const sec = "YOUR_SECRET_ID"
-const params = `?client_id=${id}&client_secret=${sec}`
-
-function getErrorMsg (message, username) {
- if (message === 'Not Found') {
- return `${username} doesn't exist`
- }
-
- return message
-}
-
-function getProfile (username) {
- return fetch(`https://api.github.com/users/${username}${params}`)
- .then((res) => res.json())
- .then((profile) => {
- if (profile.message) {
- throw new Error(getErrorMsg(profile.message, username))
- }
-
- return profile
- })
-}
-
-function getRepos (username) {
- return fetch(`https://api.github.com/users/${username}/repos${params}&per_page=100`)
- .then((res) => res.json())
- .then((repos) => {
- if (repos.message) {
- throw new Error(getErrorMsg(repos.message, username))
- }
-
- return repos
- })
-}
-
-function getStarCount (repos) {
- return repos.reduce((count, { stargazers_count }) => count + stargazers_count , 0)
-}
-
-function calculateScore (followers, repos) {
- return (followers * 3) + getStarCount(repos)
-}
-
-function getUserData (player) {
- return Promise.all([
- getProfile(player),
- getRepos(player)
- ]).then(([ profile, repos ]) => ({
- profile,
- score: calculateScore(profile.followers, repos)
- }))
-}
-
-function sortPlayers (players) {
- return players.sort((a, b) => b.score - a.score)
-}
-
-export function battle (players) {
- return Promise.all([
- getUserData(players[0]),
- getUserData(players[1])
- ]).then((results) => sortPlayers(results))
-}
-
-export function fetchPopularRepos (language) {
- const endpoint = window.encodeURI(`https://api.github.com/search/repositories?q=stars:>1+language:${language}&sort=stars&order=desc&type=Repositories`)
-
- return fetch(endpoint)
- .then((res) => res.json())
- .then((data) => {
- if (!data.items) {
- throw new Error(data.message)
- }
-
- return data.items
- })
-}
\ No newline at end of file
diff --git a/app/utils/api.ts b/app/utils/api.ts
new file mode 100644
index 0000000..8f4ec77
--- /dev/null
+++ b/app/utils/api.ts
@@ -0,0 +1,97 @@
+const id = "YOUR_CLIENT_ID";
+const sec = "YOUR_SECRET_ID";
+const params = `?client_id=${id}&client_secret=${sec}`;
+
+function getErrorMsg(message: string, username: string) {
+ if (message === "Not Found") {
+ return `${username} doesn't exist`;
+ }
+
+ return message;
+}
+
+export interface User {
+ id: string;
+ followers: number;
+}
+function getProfile(username: string): Promise {
+ return fetch(`https://api.github.com/users/${username}${params}`)
+ .then((res) => res.json())
+ .then((profile) => {
+ if (profile.message) {
+ throw new Error(getErrorMsg(profile.message, username));
+ }
+
+ return profile;
+ });
+}
+
+export interface Repo {
+ id: string;
+ stargazers_count: number;
+}
+
+function getRepos(username: string): Promise {
+ return fetch(
+ `https://api.github.com/users/${username}/repos${params}&per_page=100`
+ )
+ .then((res) => res.json())
+ .then((repos) => {
+ if (repos.message) {
+ throw new Error(getErrorMsg(repos.message, username));
+ }
+
+ return repos;
+ });
+}
+
+function getStarCount(repos: Repo[]) {
+ return repos.reduce(
+ (count, { stargazers_count }) => count + stargazers_count,
+ 0
+ );
+}
+
+function calculateScore(followers: number, repos: Repo[]) {
+ return followers * 3 + getStarCount(repos);
+}
+
+export interface Player {
+ profile: User;
+ score: number;
+}
+function getUserData(player: string): Promise {
+ return Promise.all([getProfile(player), getRepos(player)]).then(
+ ([profile, repos]) => ({
+ profile,
+ score: calculateScore(profile.followers, repos),
+ })
+ );
+}
+
+function sortPlayers(players: [Player, Player]) {
+ return players.sort((a, b) => b.score - a.score);
+}
+
+export function battle(players: [string, string]) {
+ return Promise.all([
+ getUserData(players[0]),
+ getUserData(players[1]),
+ ]).then((results) => sortPlayers(results));
+}
+
+export function fetchPopularRepos(language: string): Promise {
+ const endpoint = window.encodeURI(
+ `https://api.github.com/search/repositories?q=stars:>1+language:${language}&sort=stars&order=desc&type=Repositories`
+ );
+
+ return fetch(endpoint)
+ .then((res) => res.json())
+ .then((data) => {
+ if (!data.items) {
+ throw new Error(data.message);
+ }
+
+ return data.items;
+ });
+}
diff --git a/package-lock.json b/package-lock.json
index 689842b..16b1066 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,5 +1,5 @@
{
- "name": "github-battle",
+ "name": "github-battle-hooks",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
@@ -252,6 +252,18 @@
"@babel/types": "^7.4.4"
}
},
+ "@babel/helper-validator-identifier": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
+ "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==",
+ "dev": true
+ },
+ "@babel/helper-validator-option": {
+ "version": "7.12.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.1.tgz",
+ "integrity": "sha512-YpJabsXlJVWP0USHjnC/AQDTLlZERbON577YUVO/wLpqyj6HAtVYnWaQaN0iUN+1/tWn3c+uKKXjRut5115Y2A==",
+ "dev": true
+ },
"@babel/helper-wrap-function": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz",
@@ -418,6 +430,23 @@
"@babel/helper-plugin-utils": "^7.0.0"
}
},
+ "@babel/plugin-syntax-typescript": {
+ "version": "7.12.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.1.tgz",
+ "integrity": "sha512-UZNEcCY+4Dp9yYRCAHrHDU+9ZXLYaY9MgBXSRLkB9WjYFRR6quJBumfVrEkUxrePPBwFcpWfNKXqVRQQtm7mMA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
+ "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==",
+ "dev": true
+ }
+ }
+ },
"@babel/plugin-transform-arrow-functions": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz",
@@ -755,6 +784,179 @@
"@babel/helper-plugin-utils": "^7.0.0"
}
},
+ "@babel/plugin-transform-typescript": {
+ "version": "7.12.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.12.1.tgz",
+ "integrity": "sha512-VrsBByqAIntM+EYMqSm59SiMEf7qkmI9dqMt6RbD/wlwueWmYcI0FFK5Fj47pP6DRZm+3teXjosKlwcZJ5lIMw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.12.1",
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-typescript": "^7.12.1"
+ },
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
+ "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.10.4"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.12.10",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.10.tgz",
+ "integrity": "sha512-6mCdfhWgmqLdtTkhXjnIz0LcdVCd26wS2JXRtj2XY0u5klDsXBREA/pG5NVOuVnF2LUrBGNFtQkIqqTbblg0ww==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.10",
+ "jsesc": "^2.5.1",
+ "source-map": "^0.5.0"
+ }
+ },
+ "@babel/helper-create-class-features-plugin": {
+ "version": "7.12.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz",
+ "integrity": "sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-function-name": "^7.10.4",
+ "@babel/helper-member-expression-to-functions": "^7.12.1",
+ "@babel/helper-optimise-call-expression": "^7.10.4",
+ "@babel/helper-replace-supers": "^7.12.1",
+ "@babel/helper-split-export-declaration": "^7.10.4"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz",
+ "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.10.4",
+ "@babel/template": "^7.10.4",
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.12.10",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz",
+ "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.10"
+ }
+ },
+ "@babel/helper-member-expression-to-functions": {
+ "version": "7.12.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz",
+ "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.7"
+ }
+ },
+ "@babel/helper-optimise-call-expression": {
+ "version": "7.12.10",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz",
+ "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.10"
+ }
+ },
+ "@babel/helper-plugin-utils": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
+ "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==",
+ "dev": true
+ },
+ "@babel/helper-replace-supers": {
+ "version": "7.12.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.5.tgz",
+ "integrity": "sha512-5YILoed0ZyIpF4gKcpZitEnXEJ9UoDRki1Ey6xz46rxOzfNMAhVIJMoune1hmPVxh40LRv1+oafz7UsWX+vyWA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-member-expression-to-functions": "^7.12.1",
+ "@babel/helper-optimise-call-expression": "^7.10.4",
+ "@babel/traverse": "^7.12.5",
+ "@babel/types": "^7.12.5"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz",
+ "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.11.0"
+ }
+ },
+ "@babel/highlight": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
+ "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.10.4",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.12.10",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.10.tgz",
+ "integrity": "sha512-PJdRPwyoOqFAWfLytxrWwGrAxghCgh/yTNCYciOz8QgjflA7aZhECPZAa2VUedKg2+QMWkI0L9lynh2SNmNEgA==",
+ "dev": true
+ },
+ "@babel/template": {
+ "version": "7.12.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz",
+ "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/parser": "^7.12.7",
+ "@babel/types": "^7.12.7"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.12.10",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.10.tgz",
+ "integrity": "sha512-6aEtf0IeRgbYWzta29lePeYSk+YAFIC3kyqESeft8o5CkFlYIMX+EQDDWEiAQ9LHOA3d0oHdgrSsID/CKqXJlg==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/generator": "^7.12.10",
+ "@babel/helper-function-name": "^7.10.4",
+ "@babel/helper-split-export-declaration": "^7.11.0",
+ "@babel/parser": "^7.12.10",
+ "@babel/types": "^7.12.10",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0",
+ "lodash": "^4.17.19"
+ }
+ },
+ "@babel/types": {
+ "version": "7.12.10",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.10.tgz",
+ "integrity": "sha512-sf6wboJV5mGyip2hIpDSKsr80RszPinEFjsHTalMxZAZkoQ2/2yQzxlcFN52SJqsyPfLtPmenL4g2KB3KJXPDw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.10.4",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "lodash": {
+ "version": "4.17.20",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
+ "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
+ "dev": true
+ }
+ }
+ },
"@babel/plugin-transform-unicode-regex": {
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz",
@@ -837,6 +1039,25 @@
"@babel/plugin-transform-react-jsx-source": "^7.0.0"
}
},
+ "@babel/preset-typescript": {
+ "version": "7.12.7",
+ "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.12.7.tgz",
+ "integrity": "sha512-nOoIqIqBmHBSEgBXWR4Dv/XBehtIFcw9PqZw6rFYuKrzsZmOQm3PR5siLBnKZFEsDb03IegG8nSjU/iXXXYRmw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/helper-validator-option": "^7.12.1",
+ "@babel/plugin-transform-typescript": "^7.12.1"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
+ "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==",
+ "dev": true
+ }
+ }
+ },
"@babel/runtime": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.5.tgz",
@@ -901,6 +1122,12 @@
"@types/node": "*"
}
},
+ "@types/history": {
+ "version": "4.7.8",
+ "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz",
+ "integrity": "sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==",
+ "dev": true
+ },
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@@ -913,6 +1140,52 @@
"integrity": "sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg==",
"dev": true
},
+ "@types/prop-types": {
+ "version": "15.7.3",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
+ "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
+ "dev": true
+ },
+ "@types/react": {
+ "version": "17.0.0",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.0.tgz",
+ "integrity": "sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw==",
+ "dev": true,
+ "requires": {
+ "@types/prop-types": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "@types/react-dom": {
+ "version": "17.0.0",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.0.tgz",
+ "integrity": "sha512-lUqY7OlkF/RbNtD5nIq7ot8NquXrdFrjSOR6+w9a9RFQevGi1oZO1dcJbXMeONAPKtZ2UrZOEJ5UOCVsxbLk/g==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*"
+ }
+ },
+ "@types/react-router": {
+ "version": "5.1.8",
+ "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.8.tgz",
+ "integrity": "sha512-HzOyJb+wFmyEhyfp4D4NYrumi+LQgQL/68HvJO+q6XtuHSDvw6Aqov7sCAhjbNq3bUPgPqbdvjXC5HeB2oEAPg==",
+ "dev": true,
+ "requires": {
+ "@types/history": "*",
+ "@types/react": "*"
+ }
+ },
+ "@types/react-router-dom": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.6.tgz",
+ "integrity": "sha512-gjrxYqxz37zWEdMVvQtWPFMFj1dRDb4TGOcgyOfSXTrEXdF92L00WE3C471O3TV/RF1oskcStkXsOU0Ete4s/g==",
+ "dev": true,
+ "requires": {
+ "@types/history": "*",
+ "@types/react": "*",
+ "@types/react-router": "*"
+ }
+ },
"@webassemblyjs/ast": {
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
@@ -2206,6 +2479,12 @@
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true
},
+ "csstype": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.5.tgz",
+ "integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==",
+ "dev": true
+ },
"cyclist": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz",
@@ -6858,6 +7137,12 @@
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
"dev": true
},
+ "typescript": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz",
+ "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==",
+ "dev": true
+ },
"uglify-js": {
"version": "3.4.10",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",
diff --git a/package.json b/package.json
index 7f9e7c3..76ecaac 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,8 @@
"babel": {
"presets": [
"@babel/preset-env",
- "@babel/preset-react"
+ "@babel/preset-react",
+ "@babel/preset-typescript"
],
"plugins": [
"@babel/plugin-proposal-class-properties",
@@ -34,12 +35,17 @@
"@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@babel/preset-react": "^7.0.0",
+ "@babel/preset-typescript": "^7.12.7",
+ "@types/react": "^17.0.0",
+ "@types/react-dom": "^17.0.0",
+ "@types/react-router-dom": "^5.1.6",
"babel-loader": "^8.0.6",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"copy-webpack-plugin": "^5.0.3",
"css-loader": "^3.1.0",
"html-webpack-plugin": "^3.2.0",
"style-loader": "^0.23.1",
+ "typescript": "^4.1.3",
"webpack": "^4.36.1",
"webpack-cli": "^3.3.6",
"webpack-dev-server": "^3.7.2"
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..ff47636
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,71 @@
+{
+ "compilerOptions": {
+ /* Visit https://aka.ms/tsconfig.json to read more about this file */
+
+ /* Basic Options */
+ // "incremental": true, /* Enable incremental compilation */
+ "target": "ESNext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
+ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
+ "lib": ["DOM","ESNext"], /* Specify library files to be included in the compilation. */
+ // "allowJs": true, /* Allow javascript files to be compiled. */
+ // "checkJs": true, /* Report errors in .js files. */
+ "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
+ // "declaration": true, /* Generates corresponding '.d.ts' file. */
+ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
+ // "sourceMap": true, /* Generates corresponding '.map' file. */
+ // "outFile": "./", /* Concatenate and emit output to single file. */
+ // "outDir": "./", /* Redirect output structure to the directory. */
+ // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
+ // "composite": true, /* Enable project compilation */
+ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
+ // "removeComments": true, /* Do not emit comments to output. */
+ "noEmit": true, /* Do not emit outputs. */
+ // "importHelpers": true, /* Import emit helpers from 'tslib'. */
+ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
+ "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
+
+ /* Strict Type-Checking Options */
+ "strict": true, /* Enable all strict type-checking options. */
+ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
+ // "strictNullChecks": true, /* Enable strict null checks. */
+ // "strictFunctionTypes": true, /* Enable strict checking of function types. */
+ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
+ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
+ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
+ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
+
+ /* Additional Checks */
+ // "noUnusedLocals": true, /* Report errors on unused locals. */
+ // "noUnusedParameters": true, /* Report errors on unused parameters. */
+ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
+ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
+ // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
+
+ /* Module Resolution Options */
+ // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
+ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
+ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
+ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
+ // "typeRoots": [], /* List of folders to include type definitions from. */
+ // "types": [], /* Type declaration files to be included in compilation. */
+ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
+ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
+ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
+ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
+
+ /* Source Map Options */
+ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
+ // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
+ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
+
+ /* Experimental Options */
+ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
+ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
+
+ /* Advanced Options */
+ "skipLibCheck": true, /* Skip type checking of declaration files. */
+ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
+ },
+ "include": ["app/**/*"]
+}
diff --git a/webpack.config.js b/webpack.config.js
index b376c52..7aa6055 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,30 +1,32 @@
-const path = require('path')
-const HtmlWebpackPlugin = require('html-webpack-plugin')
-const CopyPlugin = require('copy-webpack-plugin')
+const path = require("path");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
- entry: './app/index.js',
+ entry: "./app/index.tsx",
output: {
- path: path.resolve(__dirname, 'dist'),
- filename: 'index_bundle.js',
- publicPath: '/'
+ path: path.resolve(__dirname, "dist"),
+ filename: "index_bundle.js",
+ publicPath: "/",
+ },
+ resolve: {
+ // Add `.ts` and `.tsx` as a resolvable extension.
+ extensions: [".ts", ".tsx", ".js"],
},
module: {
rules: [
- { test: /\.(js)$/, use: 'babel-loader' },
- { test: /\.css$/, use: [ 'style-loader', 'css-loader' ]}
- ]
+ { test: /\.(js|ts|tsx)$/, use: "babel-loader" },
+ { test: /\.css$/, use: ["style-loader", "css-loader"] },
+ ],
},
- mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
+ mode: process.env.NODE_ENV === "production" ? "production" : "development",
plugins: [
new HtmlWebpackPlugin({
- template: 'app/index.html'
+ template: "app/index.html",
}),
- new CopyPlugin([
- { from : '_redirects' }
- ])
+ new CopyPlugin([{ from: "_redirects" }]),
],
devServer: {
- historyApiFallback: true
- }
-}
\ No newline at end of file
+ historyApiFallback: true,
+ },
+};