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", "version": "0.4.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@fortawesome/fontawesome": "^1.1.8", "@fortawesome/fontawesome-svg-core": "^1.2.22",
"@fortawesome/fontawesome-free-solid": "^5.0.13", "@fortawesome/free-solid-svg-icons": "^5.10.2",
"@fortawesome/react-fontawesome": "^0.0.19", "@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": "^16.8.6",
"react-dom": "^16.8.6", "react-dom": "^16.8.6",
"react-router-dom": "^4.3.1", "react-router-dom": "^4.3.1",
"react-scripts": "^3.0.1" "react-scripts": "^3.0.1",
"typescript": "^3.5.3"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
@ -18,17 +24,9 @@
"eject": "react-scripts eject" "eject": "react-scripts eject"
}, },
"devDependencies": { "devDependencies": {
"csslint": "^1.0.5", "prettier": "^1.17.1",
"eslint-config-prettier": "^2.10.0", "tslint": "^5.19.0",
"eslint-config-standard": "^11.0.0", "tslint-config-prettier": "^1.18.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"
}, },
"browserslist": [ "browserslist": [
">0.2%", ">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 React from "react"
import ReactDOM from 'react-dom' import ReactDOM from "react-dom"
import Website from './Website.js' import Website from "./Website"
it('renders without crashing', () => { it("renders without crashing", () => {
const div = document.createElement('div') const div = document.createElement("div")
ReactDOM.render(<Website />, div) ReactDOM.render(<Website />, div)
ReactDOM.unmountComponentAtNode(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 * as React from "react"
import React from 'react'
import './index.css' import profile from "../../images/profile.webp"
import profile from '../../images/profile.webp' import "./index.css"
type Props = {} export default class Home extends React.PureComponent {
public render() {
class Home extends React.PureComponent<Props> {
render () {
return ( return (
<div className='home-container'> <div className="home-container">
<img src={profile} alt='Profile' /> <img src={profile} alt="Profile" />
<p>Hello and welcome,</p> <p>Hello and welcome,</p>
<p> <p>
I am a software developer, with most of my experience in web services. 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 Thank you for reading my quick bio. If you would like to contact me
visit the contact page for all of your options. visit the contact page for all of your options.
</p> </p>
<p className='signature'>- Mitchell J. F. Simon, III</p> <p className="signature">- Mitchell J. F. Simon, III</p>
</div> </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"]
}
}