Migrate project from JS+Flow to TypeScript

This commit is contained in:
Mitchell Simon 2019-09-02 21:53:03 -04:00
parent 60a557f918
commit 356b95579e
36 changed files with 3300 additions and 3058 deletions

View File

@ -1,11 +0,0 @@
extends:
- 'standard'
- 'plugin:react/recommended'
- 'plugin:flowtype/recommended'
- 'prettier'
- 'prettier/flowtype'
- 'prettier/react'
- 'prettier/standard'
plugins:
- react
- flowtype

View File

@ -1,12 +0,0 @@
[ignore]
[include]
[libs]
[lints]
all=warn
[options]
[strict]

5377
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,13 +3,19 @@
"version": "0.4.0",
"private": true,
"dependencies": {
"@fortawesome/fontawesome": "^1.1.8",
"@fortawesome/fontawesome-free-solid": "^5.0.13",
"@fortawesome/react-fontawesome": "^0.0.19",
"@fortawesome/fontawesome-svg-core": "^1.2.22",
"@fortawesome/free-solid-svg-icons": "^5.10.2",
"@fortawesome/react-fontawesome": "^0.1.4",
"@types/jest": "^24.0.18",
"@types/node": "^12.7.3",
"@types/react": "^16.9.2",
"@types/react-dom": "^16.9.0",
"@types/react-router-dom": "^4.3.5",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-router-dom": "^4.3.1",
"react-scripts": "^3.0.1"
"react-scripts": "^3.0.1",
"typescript": "^3.5.3"
},
"scripts": {
"start": "react-scripts start",
@ -18,17 +24,9 @@
"eject": "react-scripts eject"
},
"devDependencies": {
"csslint": "^1.0.5",
"eslint-config-prettier": "^2.10.0",
"eslint-config-standard": "^11.0.0",
"eslint-plugin-flowtype": "^2.50.3",
"eslint-plugin-import": "^2.17.3",
"eslint-plugin-node": "^6.0.1",
"eslint-plugin-promise": "^3.8.0",
"eslint-plugin-react": "^7.13.0",
"eslint-plugin-standard": "^3.1.0",
"flow-bin": "^0.76.0",
"prettier": "^1.17.1"
"prettier": "^1.17.1",
"tslint": "^5.19.0",
"tslint-config-prettier": "^1.18.0"
},
"browserslist": [
">0.2%",

View File

@ -1,26 +0,0 @@
// @flow
import React from 'react'
import Header from './components/Header'
import Navbar from './components/Navbar'
import Routes from './routes'
import './Website.css'
type Props = {}
class Website extends React.Component<Props> {
render () {
return (
<div className='website'>
<div className='main-container'>
<Navbar />
<Header />
<Routes />
</div>
</div>
)
}
}
export default Website

View File

@ -1,9 +1,9 @@
import React from 'react'
import ReactDOM from 'react-dom'
import Website from './Website.js'
import React from "react"
import ReactDOM from "react-dom"
import Website from "./Website"
it('renders without crashing', () => {
const div = document.createElement('div')
it("renders without crashing", () => {
const div = document.createElement("div")
ReactDOM.render(<Website />, div)
ReactDOM.unmountComponentAtNode(div)
})

21
src/Website.tsx Normal file
View File

@ -0,0 +1,21 @@
import React from "react"
import Header from "./components/Header"
import Navbar from "./components/Navbar"
import Routes from "./routes"
import "./Website.css"
export default class Website extends React.Component {
public render() {
return (
<div className="website">
<div className="main-container">
<Navbar />
<Header />
<Routes />
</div>
</div>
)
}
}

View File

@ -1,21 +0,0 @@
// @flow
import React from 'react'
import './index.css'
type Props = {
href: string,
children: string
}
class ClearButton extends React.PureComponent<Props> {
render () {
return (
<a className='clear-button' href={this.props.href}>
{this.props.children}
</a>
)
}
}
export default ClearButton

View File

@ -0,0 +1,18 @@
import React from "react"
import "./index.css"
type Props = {
href: string
children: string
}
export default class ClearButton extends React.PureComponent<Props> {
public render() {
return (
<a className="clear-button" href={this.props.href}>
{this.props.children}
</a>
)
}
}

View File

@ -1,26 +0,0 @@
// @flow
import React from 'react'
import SmallText from '../SmallText'
import './index.css'
type Props = {}
class Header extends React.PureComponent<Props> {
render () {
return (
<div className='header-container'>
<h2>Mitchell J. F. Simon</h2>
<SmallText>
Software engineer;&nbsp;
<span style={{ display: 'inline-block' }}>
cloud-native web services and clients
</span>
</SmallText>
</div>
)
}
}
export default Header

View File

@ -0,0 +1,21 @@
import React from "react"
import SmallText from "../SmallText"
import "./index.css"
export default class Header extends React.PureComponent {
public render() {
return (
<div className="header-container">
<h2>Mitchell J. F. Simon</h2>
<SmallText>
Software engineer;&nbsp;
<span style={{ display: "inline-block" }}>
cloud-native web services and clients
</span>
</SmallText>
</div>
)
}
}

View File

@ -1,37 +0,0 @@
// @flow
import * as React from 'react'
import SmallText from '../../components/SmallText'
import './index.css'
type Props = {
title: string,
company: string,
timeSpan: string,
bullets: Array<string>
}
class Experience extends React.PureComponent<Props> {
listedBullets: Array<React.Element<string>>
constructor (props: Props) {
super(props)
this.listedBullets = this.props.bullets.map((bullet, index) => (
<li key={index}>{bullet}</li>
))
}
render () {
return (
<div className='job-container'>
<div className='job-title'>{this.props.title}</div>
<div className='job-company'>{this.props.company}</div>
<SmallText>{this.props.timeSpan}</SmallText>
<ul>{this.listedBullets}</ul>
</div>
)
}
}
export default Experience

View File

@ -0,0 +1,29 @@
import * as React from "react"
import SmallText from "../../components/SmallText"
import "./index.css"
type Props = {
title: string
company: string
timeSpan: string
bullets: string[]
}
export default class Experience extends React.PureComponent<Props> {
public render() {
return (
<div className="job-container">
<div className="job-title">{this.props.title}</div>
<div className="job-company">{this.props.company}</div>
<SmallText>{this.props.timeSpan}</SmallText>
<ul>{this.renderBullets()}</ul>
</div>
)
}
private renderBullets() {
return this.props.bullets.map((bullet, index) => (
<li key={index}>{bullet}</li>
))
}
}

View File

@ -1,88 +0,0 @@
// @flow
import * as React from 'react'
import { NavLink } from 'react-router-dom'
import FontAwesomeIcon from '@fortawesome/react-fontawesome'
import faBars from '@fortawesome/fontawesome-free-solid/faBars'
import './index.css'
import { routes } from '../../routes/routes.js'
const changeMenu = 800
type Props = {}
type State = {
showMenu: boolean
}
class Navbar extends React.Component<Props, State> {
buttons: Array<NavLink<string>>
constructor (props: Props) {
super(props)
if (window.innerWidth <= changeMenu) {
this.state = { showMenu: false }
} else {
this.state = { showMenu: true }
}
this.buttons = routes.map(route => (
<NavLink
key={route.name}
className='navbar-menu-button'
activeClassName='active-button'
exact={route.exact}
to={route.path}
onClick={this.closeMenu}
>
{route.name}
</NavLink>
))
}
isMobile = () => window.innerWidth <= changeMenu
closeMenu = () => {
window.scrollTo(0, 0)
if (this.isMobile()) {
this.setState({ showMenu: false })
}
}
toggleMenu = () => {
if (this.isMobile() || !this.state.showMenu) {
this.setState(prev => ({ showMenu: !prev.showMenu }))
}
}
render () {
let menuButton: ?React.Element<string>
if (this.isMobile()) {
menuButton = (
<div
className={
this.state.showMenu
? 'navbar-button navbar-button-closed'
: 'navbar-button'
}
onClick={this.toggleMenu}
>
<FontAwesomeIcon icon={faBars} />
</div>
)
}
return (
<div className='navbar'>
{menuButton}
{this.state.showMenu ? (
<div className='navbar-menu'>{this.buttons}</div>
) : null}
</div>
)
}
}
export default Navbar

View File

@ -0,0 +1,81 @@
import { faBars } from "@fortawesome/free-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import * as React from "react"
import { NavLink } from "react-router-dom"
import { routes } from "../../routes/routes"
import "./index.css"
type State = {
showMenu: boolean
}
class Navbar extends React.Component<{}, State> {
constructor(props: {}) {
super(props)
this.state = { showMenu: !this.isMobile() }
}
public render() {
let menuButton: JSX.Element | null = null
if (this.isMobile()) {
menuButton = (
<div
className={
this.state.showMenu
? "navbar-button navbar-button-closed"
: "navbar-button"
}
onClick={this.toggleMenu}
>
<FontAwesomeIcon icon={faBars} />
</div>
)
}
return (
<div className="navbar">
{menuButton}
{this.state.showMenu ? (
<div className="navbar-menu">{this.renderButtons()}</div>
) : null}
</div>
)
}
private isMobile() {
const mobileMenuMaximum = 800
return window.innerWidth <= mobileMenuMaximum
}
private toggleMenu() {
if (this.isMobile() || !this.state.showMenu) {
this.setState(prev => ({ showMenu: !prev.showMenu }))
}
}
private renderButtons() {
return routes.map(route => (
<NavLink
key={route.name}
className="navbar-menu-button"
activeClassName="active-button"
exact={route.exact}
to={route.path}
onClick={this.closeMenu}
>
{route.name}
</NavLink>
))
}
private closeMenu() {
window.scrollTo(0, 0)
if (this.isMobile()) {
this.setState({ showMenu: false })
}
}
}
export default Navbar

View File

@ -1,42 +0,0 @@
// @flow
import * as React from 'react'
import ClearButton from '../../components/ClearButton'
import './index.css'
type Badge = {
imgUrl: string,
linkUrl: string,
alt: string
}
type Props = {
title: string,
children: string,
repoUrl: string,
badges: Array<Badge>
}
class Project extends React.PureComponent<Props> {
renderBadges = (badges: Array<Badge>) => {
return this.props.badges.map((badge, index) => (
<a className='project-badge' href={badge.linkUrl} key={index}>
<img src={badge.imgUrl} alt={badge.alt} />
</a>
))
}
render () {
return (
<div className='project-container'>
<h4>{this.props.title}</h4>
{this.renderBadges(this.props.badges)}
<p>{this.props.children}</p>
<ClearButton href={this.props.repoUrl}>Repository</ClearButton>
</div>
)
}
}
export default Project

View File

@ -0,0 +1,39 @@
import * as React from "react"
import ClearButton from "../../components/ClearButton"
import "./index.css"
type Badge = {
imgUrl: string
linkUrl: string
alt: string
}
type Props = {
title: string
children: string
repoUrl: string
badges: Badge[]
}
export default class Project extends React.PureComponent<Props> {
public render() {
return (
<div className="project-container">
<h4>{this.props.title}</h4>
{this.renderBadges(this.props.badges)}
<p>{this.props.children}</p>
<ClearButton href={this.props.repoUrl}>Repository</ClearButton>
</div>
)
}
private renderBadges(badges: Badge[]) {
return this.props.badges.map((badge, index) => (
<a className="project-badge" href={badge.linkUrl} key={index}>
<img src={badge.imgUrl} alt={badge.alt} />
</a>
))
}
}

View File

@ -1,16 +0,0 @@
// @flow
import React from 'react'
import './index.css'
type Props = {
children: string
}
class SmallText extends React.PureComponent<Props> {
render () {
return <div className='small-text'>{this.props.children}</div>
}
}
export default SmallText

View File

@ -0,0 +1,10 @@
// @flow
import React from "react"
import "./index.css"
export default class SmallText extends React.PureComponent {
public render() {
return <div className="small-text">{this.props.children}</div>
}
}

4
src/images/webp.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module "*.webp" {
const value: any
export default value
}

View File

@ -1,15 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import './index.css'
import Website from './Website'
import registerServiceWorker from './registerServiceWorker'
ReactDOM.render(
<BrowserRouter>
<Website />
</BrowserRouter>,
document.getElementById('root')
)
registerServiceWorker()

15
src/index.tsx Normal file
View File

@ -0,0 +1,15 @@
import React from "react"
import ReactDOM from "react-dom"
import { BrowserRouter } from "react-router-dom"
import "./index.css"
import registerServiceWorker from "./registerServiceWorker"
import Website from "./Website"
ReactDOM.render(
<BrowserRouter>
<Website />
</BrowserRouter>,
document.getElementById("root")
)
registerServiceWorker()

1
src/react-app-env.d.ts vendored Normal file
View File

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

View File

@ -1,40 +0,0 @@
// @flow
import React from 'react'
import { Route, Switch } from 'react-router-dom'
import { routes, redirects } from './routes.js'
type Props = {}
class Routes extends React.Component<Props> {
routes: Array<Route>
redirects: Array<Route>
constructor (props: Props) {
super(props)
this.routes = routes.map(route => (
<Route
key={route.name}
exact={route.exact}
path={route.path}
component={route.component}
/>
))
this.redirects = redirects.map(route => (
<Route key={route.path} path={route.path} render={route.func} />
))
}
render () {
return (
<Switch>
{this.routes}
{this.redirects}
</Switch>
)
}
}
export default Routes

32
src/routes/index.tsx Normal file
View File

@ -0,0 +1,32 @@
import React from "react"
import { Route, Switch } from "react-router-dom"
import { redirects, routes } from "./routes"
export default class Routes extends React.Component {
public render() {
return (
<Switch>
{this.renderRoutes()}
{this.renderRedirects()}
</Switch>
)
}
private renderRoutes() {
return routes.map(route => (
<Route
key={route.name}
exact={route.exact}
path={route.path}
component={route.component}
/>
))
}
private renderRedirects() {
return redirects.map(route => (
<Route key={route.path} path={route.path} render={route.func} />
))
}
}

View File

@ -1,34 +0,0 @@
// @flow
import Home from '../screens/Home'
import Projects from '../screens/Projects'
import Contact from '../screens/Contact'
const routes = [
{
path: '/',
name: 'Home',
component: Home,
exact: true
},
{
path: '/projects',
name: 'Projects',
component: Projects,
exact: false
},
{
path: '/contact',
name: 'Contact',
component: Contact,
exact: false
}
]
const redirects = [
{
path: '/linkedin',
func: () => (window.location = 'https://linkedin.com/in/mitchelljfsimon')
}
]
export { routes, redirects }

34
src/routes/routes.tsx Normal file
View File

@ -0,0 +1,34 @@
import Contact from "../screens/Contact"
import Home from "../screens/Home"
import Projects from "../screens/Projects"
export const routes = [
{
component: Home,
exact: true,
name: "Home",
path: "/"
},
{
component: Projects,
exact: false,
name: "Projects",
path: "/projects"
},
{
component: Contact,
exact: false,
name: "Contact",
path: "/contact"
}
]
export const redirects = [
{
func: () => {
window.location.replace("https://linkedin.com/in/mitchelljfsimon")
return null
},
path: "/linkedin"
}
]

View File

@ -1,22 +0,0 @@
// @flow
import React from 'react'
import './index.css'
import linkedIn from '../../images/In-2C-128px-TM.png'
type Props = {}
class Contact extends React.PureComponent<Props> {
render () {
return (
<div className='contact-container'>
<p>m@mjfs.us</p>
<a href='https://www.linkedin.com/in/mitchelljfsimon/'>
<img src={linkedIn} alt='LinkedIn' />
</a>
</div>
)
}
}
export default Contact

View File

@ -0,0 +1,17 @@
import React from "react"
import linkedIn from "../../images/In-2C-128px-TM.png"
import "./index.css"
export default class Contact extends React.PureComponent {
public render() {
return (
<div className="contact-container">
<p>m@mjfs.us</p>
<a href="https://www.linkedin.com/in/mitchelljfsimon/">
<img src={linkedIn} alt="LinkedIn" />
</a>
</div>
)
}
}

View File

@ -1,38 +0,0 @@
// @flow
import * as React from 'react'
import './index.css'
import JobComponent from '../../components/Job'
import jobsData from '../../data/jobs.json'
type Props = {}
type Job = {
timeSpan: string,
title: string,
company: string,
bullets: Array<string>
}
class Experience extends React.PureComponent<Props> {
renderJobs = (jobs: Array<Job>) => {
return jobs.map(job => (
<JobComponent
key={job.title}
title={job.title}
company={job.company}
timeSpan={job.timeSpan}
bullets={job.bullets}
/>
))
}
render () {
return (
<div className='experience-container'>{this.renderJobs(jobsData)}</div>
)
}
}
export default Experience

View File

@ -0,0 +1,32 @@
import * as React from "react"
import Job from "../../components/Job"
import jobsData from "../../data/jobs.json"
import "./index.css"
type JobData = {
title: string
company: string
timeSpan: string
bullets: string[]
}
export default class Experience extends React.PureComponent {
public render() {
return (
<div className="experience-container">{this.renderJobs(jobsData)}</div>
)
}
private renderJobs(jobs: JobData[]) {
return jobs.map(job => (
<Job
key={job.title}
title={job.title}
company={job.company}
timeSpan={job.timeSpan}
bullets={job.bullets}
/>
))
}
}

View File

@ -1,16 +1,13 @@
// @flow
import React from 'react'
import * as React from "react"
import './index.css'
import profile from '../../images/profile.webp'
import profile from "../../images/profile.webp"
import "./index.css"
type Props = {}
class Home extends React.PureComponent<Props> {
render () {
export default class Home extends React.PureComponent {
public render() {
return (
<div className='home-container'>
<img src={profile} alt='Profile' />
<div className="home-container">
<img src={profile} alt="Profile" />
<p>Hello and welcome,</p>
<p>
I am a software developer, with most of my experience in web services.
@ -32,10 +29,8 @@ class Home extends React.PureComponent<Props> {
Thank you for reading my quick bio. If you would like to contact me
visit the contact page for all of your options.
</p>
<p className='signature'>- Mitchell J. F. Simon, III</p>
<p className="signature">- Mitchell J. F. Simon, III</p>
</div>
)
}
}
export default Home

View File

@ -1,48 +0,0 @@
// @flow
import * as React from 'react'
import ProjectComponent from '../../components/Project'
import './index.css'
import projectsData from '../../data/projects.json'
type Props = {}
type Badge = {
imgUrl: string,
linkUrl: string,
alt: string
}
type Project = {
title: string,
repoUrl: string,
badges: Array<Badge>,
description: string
}
class Projects extends React.PureComponent<Props> {
renderProjects = (projects: Array<Project>) => {
return projects.map(project => (
<ProjectComponent
key={project.title}
title={project.title}
repoUrl={project.repoUrl}
badges={project.badges}
>
{project.description}
</ProjectComponent>
))
}
render () {
return (
<div className='projects-container'>
{this.renderProjects(projectsData)}
</div>
)
}
}
export default Projects

View File

@ -0,0 +1,44 @@
// @flow
import * as React from "react"
import Project from "../../components/Project"
import "./index.css"
import projectsData from "../../data/projects.json"
type BadgeData = {
imgUrl: string
linkUrl: string
alt: string
}
type ProjectData = {
title: string
repoUrl: string
badges: BadgeData[]
description: string
}
export default class Projects extends React.PureComponent {
public render() {
return (
<div className="projects-container">
{this.renderProjects(projectsData)}
</div>
)
}
private renderProjects(projects: ProjectData[]) {
return projects.map(project => (
<Project
key={project.title}
title={project.title}
repoUrl={project.repoUrl}
badges={project.badges}
>
{project.description}
</Project>
))
}
}

36
tsconfig.json Normal file
View File

@ -0,0 +1,36 @@
{
"compilerOptions": {
"outDir": "build/dist",
"module": "esnext",
"target": "es5",
"lib": ["es6", "dom"],
"sourceMap": true,
"allowJs": true,
"jsx": "react",
"moduleResolution": "node",
"rootDir": "src",
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
},
"exclude": [
"node_modules",
"build",
"scripts",
"acceptance-tests",
"webpack",
"jest",
"src/setupTests.ts"
],
"types": ["typePatches"],
"include": ["src"]
}

12
tslint.json Normal file
View File

@ -0,0 +1,12 @@
{
"extends": ["tslint:latest", "tslint-config-prettier"],
"defaultSeverity": "warning",
"rules": {
"semicolon": false,
"resolve-json-module": true,
"interface-over-type-literal": false
},
"linterOptions": {
"exclude": ["config/**/*.js", "node_modules/**/*.ts"]
}
}