diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000..c95e241
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,4 @@
+{
+ "presets": ["es2015", "next/babel"],
+ "plugins": ["styled-components-require"]
+}
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..8fa06d7
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,3 @@
+build
+node_modules
+coverage
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..9218929
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,50 @@
+{
+ "parser": "babel-eslint",
+ "plugins": [
+ "react"
+ ],
+ "ecmaFeatures": {
+ "modules": true,
+ "jsx": true,
+ "experimentalObjectRestSpread": true
+ },
+ "env": {
+ "amd": true,
+ "browser": true,
+ "es6": true,
+ "jquery": true,
+ "node": true
+ },
+ "rules": {
+ "comma-dangle": [2, "never"],
+ "no-cond-assign": 2,
+ "no-debugger": 2,
+ "no-dupe-args": 2,
+ "no-dupe-keys": 2,
+ "no-duplicate-case": 2,
+ "no-empty": 2,
+ "no-empty-function": 2,
+ "no-func-assign": 2,
+ "no-sparse-arrays": 2,
+ "eqeqeq": 2,
+ "no-eval": 2,
+ "no-magic-numbers": 0,
+ "no-lone-blocks": 2,
+ "no-redeclare": 2,
+ "no-unused-expressions": 0,
+ "no-useless-concat": 2,
+ "no-unused-vars": 2,
+ "no-use-before-define": 2,
+ "no-lonely-if": 2,
+ "no-mixed-spaces-and-tabs": 2,
+ "new-cap": 2,
+ "indent": [2, 2],
+ "vars-on-top": 2,
+ "semi": [2, "never"],
+ "react/jsx-uses-vars": 2,
+ "react/jsx-uses-react": 2
+ },
+ "parserOptions": {
+ "sourceType": "module",
+ }
+}
diff --git a/.gitignore b/.gitignore
index fd318ae..aad8cc4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,4 +30,4 @@ build/Release
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules
-npm-janitor/bundle.js
+.next
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..afb3524
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,8 @@
+language: node_js
+node_js:
+ - "7"
+cache:
+ directories:
+ - node_modules
+notifications:
+ email: false
diff --git a/api/__mocks__/user.js b/api/__mocks__/user.js
new file mode 100644
index 0000000..5dfdbf6
--- /dev/null
+++ b/api/__mocks__/user.js
@@ -0,0 +1,14 @@
+let modules = [{
+ name: 'npm-janitor-web',
+ valid: true,
+ errors: [],
+ warnings: ['Add recommended field: contributors'],
+ suggestions: ['Add optional field: homepage']
+}]
+
+const get = username => new Promise(resolve => {
+ if (username) resolve({modules: modules})
+ else resolve({modules: []})
+})
+
+export default get
diff --git a/api/user.js b/api/user.js
new file mode 100644
index 0000000..1b7cfd1
--- /dev/null
+++ b/api/user.js
@@ -0,0 +1,20 @@
+import axios from 'axios'
+
+const get = (username) => {
+ if (!username) return {modules: []}
+ return axios.get(`https://npm-janitor-api.now.sh/${username}`)
+ .then(response => {
+ let modules = response.data
+ modules = modules.map(module => ({
+ name: module.module,
+ valid: module.info.valid,
+ errors: module.info.errors || [],
+ warnings: module.info.warnings || [],
+ suggestions: module.info.recommendations || []
+ }))
+ return {modules: modules}
+ })
+ .catch(error => ({error: error.response.data.message}))
+}
+
+export default get
diff --git a/components/HotLink.jsx b/components/HotLink.jsx
deleted file mode 100644
index 7e69f68..0000000
--- a/components/HotLink.jsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import React from 'react';
-import Cards from './cards.jsx';
-import Loader from 'react-loader';
-
-class HotLink extends React.Component {
- constructor (props) {
- super(props);
- this.state = {json: [], loaded:false};
- }
- _fetch (name) {
- fetch(`https://npm-janitor.herokuapp.com/api/${name.toLowerCase()}`).
- then((data) => data.json()).
- then((json) => this.setState({json, loaded: true}));
- }
- render() {
- this._fetch(this.props.params.user);
- return
-
-
- }
-}
-
-HotLink.contextTypes = {
- router: React.PropTypes.func.isRequired
-};
-
-export default HotLink;
diff --git a/components/__snapshots__/card.test.js.snap b/components/__snapshots__/card.test.js.snap
new file mode 100644
index 0000000..c12c620
--- /dev/null
+++ b/components/__snapshots__/card.test.js.snap
@@ -0,0 +1,7 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+`;
diff --git a/components/__snapshots__/code.test.js.snap b/components/__snapshots__/code.test.js.snap
new file mode 100644
index 0000000..0a1acdb
--- /dev/null
+++ b/components/__snapshots__/code.test.js.snap
@@ -0,0 +1,7 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+`;
diff --git a/components/__snapshots__/grid.test.js.snap b/components/__snapshots__/grid.test.js.snap
new file mode 100644
index 0000000..df26f31
--- /dev/null
+++ b/components/__snapshots__/grid.test.js.snap
@@ -0,0 +1,17 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+`;
diff --git a/components/__snapshots__/header.test.js.snap b/components/__snapshots__/header.test.js.snap
new file mode 100644
index 0000000..6ceaa18
--- /dev/null
+++ b/components/__snapshots__/header.test.js.snap
@@ -0,0 +1,33 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+
+

+
+
+
+`;
diff --git a/components/__snapshots__/heavy.test.js.snap b/components/__snapshots__/heavy.test.js.snap
new file mode 100644
index 0000000..ddf1a81
--- /dev/null
+++ b/components/__snapshots__/heavy.test.js.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+ This is some bold text
+
+`;
diff --git a/components/__snapshots__/hint.test.js.snap b/components/__snapshots__/hint.test.js.snap
new file mode 100644
index 0000000..0db163f
--- /dev/null
+++ b/components/__snapshots__/hint.test.js.snap
@@ -0,0 +1,8 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+`;
diff --git a/components/__snapshots__/input.test.js.snap b/components/__snapshots__/input.test.js.snap
new file mode 100644
index 0000000..8b8a56e
--- /dev/null
+++ b/components/__snapshots__/input.test.js.snap
@@ -0,0 +1,8 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+`;
diff --git a/components/__snapshots__/link.test.js.snap b/components/__snapshots__/link.test.js.snap
new file mode 100644
index 0000000..2a8d239
--- /dev/null
+++ b/components/__snapshots__/link.test.js.snap
@@ -0,0 +1,8 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+`;
diff --git a/components/__snapshots__/loading.test.js.snap b/components/__snapshots__/loading.test.js.snap
new file mode 100644
index 0000000..10bc94b
--- /dev/null
+++ b/components/__snapshots__/loading.test.js.snap
@@ -0,0 +1,7 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+`;
diff --git a/components/__snapshots__/logo.test.js.snap b/components/__snapshots__/logo.test.js.snap
new file mode 100644
index 0000000..e533ebf
--- /dev/null
+++ b/components/__snapshots__/logo.test.js.snap
@@ -0,0 +1,15 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders big version 1`] = `
+
+`;
+
+exports[`renders small version 1`] = `
+
+`;
diff --git a/components/__snapshots__/module.test.js.snap b/components/__snapshots__/module.test.js.snap
new file mode 100644
index 0000000..772d600
--- /dev/null
+++ b/components/__snapshots__/module.test.js.snap
@@ -0,0 +1,67 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders invalid module 1`] = `
+
+
+
+ module
+
+
+
+
+
+
+ 1
+
+ error
+
+
+ -
+ name is not allowed
+
+
+
+
+
+
+
+
+`;
+
+exports[`renders valid module 1`] = `
+
+
+
+ module
+
+
+
+
+
+
+
+
+
+`;
diff --git a/components/__snapshots__/score.test.js.snap b/components/__snapshots__/score.test.js.snap
new file mode 100644
index 0000000..b60d0b7
--- /dev/null
+++ b/components/__snapshots__/score.test.js.snap
@@ -0,0 +1,19 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly when module has errors 1`] = `
+
+`;
+
+exports[`renders correctly when score <= 6 1`] = `
+
+`;
+
+exports[`renders correctly when score > 6 1`] = `
+
+`;
diff --git a/components/__snapshots__/tips.test.js.snap b/components/__snapshots__/tips.test.js.snap
new file mode 100644
index 0000000..c25467f
--- /dev/null
+++ b/components/__snapshots__/tips.test.js.snap
@@ -0,0 +1,47 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+
+
+
+ 1
+
+ error
+
+
+ -
+ name is not valid
+
+
+
+
+ Add recommended
+ field
+ :
+
+
+ contributors
+
+
+
+
+
+
+ Add optional
+ field
+ :
+
+
+ engines
+
+
+
+
+
+
+`;
diff --git a/components/card.js b/components/card.js
new file mode 100644
index 0000000..fbbdeb1
--- /dev/null
+++ b/components/card.js
@@ -0,0 +1,10 @@
+const Card = styled.span`
+ background: #FFF;
+ margin: 10px;
+ padding: 20px;
+ display: inline-block;
+ border-radius: 2px;
+ box-shadow: 0px 1px 2px #DDD;
+`
+
+export default Card
diff --git a/components/card.test.js b/components/card.test.js
new file mode 100644
index 0000000..41e12b5
--- /dev/null
+++ b/components/card.test.js
@@ -0,0 +1,10 @@
+import React from 'react'
+import Card from './card'
+import renderer from 'react-test-renderer'
+
+it('renders correctly', () => {
+ const tree = renderer.create(
+
+ ).toJSON()
+ expect(tree).toMatchSnapshot()
+})
diff --git a/components/cards.jsx b/components/cards.jsx
deleted file mode 100644
index fc36b36..0000000
--- a/components/cards.jsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-import Card from 'react-material-card';
-
-export default class Cards extends React.Component {
- constructor (props) {
- super(props);
- }
-
- render () {
- return
- {this.props.modules.map((d) =>{
- d.info.warnings = d.info.warnings || []
- d.info.errors = d.info.errors || []
- d.info.recommendations = d.info.recommendations || []
- return
-
- { d.info.warnings.map( (v) => {v}
)}
- { d.info.errors.map( (v) => {v}
)}
- { d.info.recommendations.map( (v) => {v}
)}
-
- })}
-
- }
-}
diff --git a/components/code.js b/components/code.js
new file mode 100644
index 0000000..fa04b37
--- /dev/null
+++ b/components/code.js
@@ -0,0 +1,7 @@
+const Code = styled.span`
+ font-family: monospace;
+ font-size: 15px;
+ color: #555;
+`
+
+export default Code
diff --git a/components/code.test.js b/components/code.test.js
new file mode 100644
index 0000000..230e0d7
--- /dev/null
+++ b/components/code.test.js
@@ -0,0 +1,10 @@
+import React from 'react'
+import Code from './code'
+import renderer from 'react-test-renderer'
+
+it('renders correctly', () => {
+ const tree = renderer.create(
+
+ ).toJSON()
+ expect(tree).toMatchSnapshot()
+})
diff --git a/components/grid.js b/components/grid.js
new file mode 100644
index 0000000..3e06002
--- /dev/null
+++ b/components/grid.js
@@ -0,0 +1,7 @@
+const Grid = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-around;
+`
+
+export default Grid
diff --git a/components/grid.test.js b/components/grid.test.js
new file mode 100644
index 0000000..428f1cf
--- /dev/null
+++ b/components/grid.test.js
@@ -0,0 +1,14 @@
+import React from 'react'
+import Grid from './grid'
+import renderer from 'react-test-renderer'
+
+it('renders correctly', () => {
+ const tree = renderer.create(
+
+ 1
+ 2
+ 3
+
+ ).toJSON()
+ expect(tree).toMatchSnapshot()
+})
diff --git a/components/header.js b/components/header.js
new file mode 100644
index 0000000..b5aa3c6
--- /dev/null
+++ b/components/header.js
@@ -0,0 +1,67 @@
+import Router from 'next/router'
+import Logo from './logo'
+import Input from './input'
+import Loading from './loading'
+import Hint from './hint'
+
+const timingFunction = 'cubic-bezier(0.19, 1, 0.22, 1)'
+
+const Wrapper = styled.div`
+ height: ${props => props.full ? '100vh' : '200px'};
+ background: #3EA6D6;
+ text-align: center;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: height 1s ${timingFunction};
+`
+
+const Header = class extends React.Component {
+ constructor (props) {
+ super(props)
+ this.state = {
+ username: props.username || '',
+ full: props.full,
+ loading: false
+ }
+ }
+
+ componentWillReceiveProps (props) {
+ this.setState({full: props.full, loading: props.loading})
+ }
+
+ onChange = event => this.setState({username: event.target.value})
+
+ onKeyUp = event => {
+ if (event.which === 13 && this.state.username) {
+ Router.push(`/user?name=${this.state.username}`)
+ /*
+ The user page is rendered on the server,
+ which takes a little time, we can animate
+ the header to give a perception of better transition
+ */
+ this.setState({loading: true, full: true})
+ }
+ }
+
+ render () {
+ return
+
+
+ }
+}
+
+export default Header
diff --git a/components/header.test.js b/components/header.test.js
new file mode 100644
index 0000000..99ce90f
--- /dev/null
+++ b/components/header.test.js
@@ -0,0 +1,19 @@
+import React from 'react'
+import Header from './header'
+import renderer from 'react-test-renderer'
+
+jest.mock('next/router')
+
+it('renders correctly', () => {
+ let props = {full: false}
+ const component = renderer.create()
+ let tree = component.toJSON()
+
+ let input = tree.children[0].children[1].children[0]
+ input.props.onChange({target: {value: 'siddharthkp'}})
+ input.props.onKeyUp({which: 65})
+ input.props.onKeyUp({which: 13})
+
+ tree = component.toJSON()
+ expect(tree).toMatchSnapshot()
+})
diff --git a/components/heavy.js b/components/heavy.js
new file mode 100644
index 0000000..76f7a2f
--- /dev/null
+++ b/components/heavy.js
@@ -0,0 +1,7 @@
+const Heavy = styled.div`
+ font-weight: 700;
+ margin-bottom: 10px;
+ color: ${props => props.color ? props.color : 'inherit'};
+`
+
+export default Heavy
diff --git a/components/heavy.test.js b/components/heavy.test.js
new file mode 100644
index 0000000..01a0ccd
--- /dev/null
+++ b/components/heavy.test.js
@@ -0,0 +1,10 @@
+import React from 'react'
+import Heavy from './heavy'
+import renderer from 'react-test-renderer'
+
+it('renders correctly', () => {
+ const tree = renderer.create(
+ This is some bold text
+ ).toJSON()
+ expect(tree).toMatchSnapshot()
+})
diff --git a/components/hint.js b/components/hint.js
new file mode 100644
index 0000000..6702571
--- /dev/null
+++ b/components/hint.js
@@ -0,0 +1,19 @@
+import {keyframes} from 'styled-components'
+
+const blink = keyframes`
+ 0% {opacity: 0}
+ 50% {opacity: 1}
+ 100% {opacity: 0}
+`
+
+const Hint = styled.span`
+ margin: 20px auto 0;
+ height: 25px;
+ color: #777;
+ position: relative;
+ left: -40px;
+ opacity: ${props => props.show ? 1 : 0};
+ animation: ${props => props.blink ? blink : 'none'} 2s linear infinite;
+`
+
+export default Hint
diff --git a/components/hint.test.js b/components/hint.test.js
new file mode 100644
index 0000000..0e13405
--- /dev/null
+++ b/components/hint.test.js
@@ -0,0 +1,10 @@
+import React from 'react'
+import Hint from './hint'
+import renderer from 'react-test-renderer'
+
+it('renders correctly', () => {
+ const tree = renderer.create(
+
+ ).toJSON()
+ expect(tree).toMatchSnapshot()
+})
diff --git a/components/images/minion.gif b/components/images/minion.gif
deleted file mode 100644
index 0ea9466..0000000
Binary files a/components/images/minion.gif and /dev/null differ
diff --git a/components/input.js b/components/input.js
new file mode 100644
index 0000000..9c48c0c
--- /dev/null
+++ b/components/input.js
@@ -0,0 +1,19 @@
+const timingFunction = 'cubic-bezier(0.19, 1, 0.22, 1)'
+
+const Input = styled.input`
+ border: none;
+ font-size: 18px;
+ padding: 20px;
+ width: ${props => props.full ? '80vw' : '30vw'}
+ margin: 10px;
+ margin-top: -3px;
+ outline: none;
+ color: #333;
+ font-family: 'Lato', sans-serif;
+ transition: width 1s ${timingFunction};
+ @media (max-width: 720px) {
+ width: 80vw;
+ }
+`
+
+export default Input
diff --git a/components/input.test.js b/components/input.test.js
new file mode 100644
index 0000000..564911e
--- /dev/null
+++ b/components/input.test.js
@@ -0,0 +1,10 @@
+import React from 'react'
+import Input from './input'
+import renderer from 'react-test-renderer'
+
+it('renders correctly', () => {
+ const tree = renderer.create(
+
+ ).toJSON()
+ expect(tree).toMatchSnapshot()
+})
diff --git a/components/legend.jsx b/components/legend.jsx
deleted file mode 100644
index bc758a4..0000000
--- a/components/legend.jsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-
-export default class Legend extends React.Component {
- constructor (props) {
- super(props);
- }
-
- render () {
-
- }
-}
diff --git a/components/link.js b/components/link.js
new file mode 100644
index 0000000..58dc485
--- /dev/null
+++ b/components/link.js
@@ -0,0 +1,6 @@
+const Link = styled.a`
+ color: #3EA6D6;
+ text-decoration: none;
+`
+
+export default Link
diff --git a/components/link.test.js b/components/link.test.js
new file mode 100644
index 0000000..131d313
--- /dev/null
+++ b/components/link.test.js
@@ -0,0 +1,10 @@
+import React from 'react'
+import Link from './link'
+import renderer from 'react-test-renderer'
+
+it('renders correctly', () => {
+ const tree = renderer.create(
+
+ ).toJSON()
+ expect(tree).toMatchSnapshot()
+})
diff --git a/components/loading.js b/components/loading.js
new file mode 100644
index 0000000..faf9029
--- /dev/null
+++ b/components/loading.js
@@ -0,0 +1,19 @@
+import {keyframes} from 'styled-components'
+
+const spin = keyframes`
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+`
+
+const Loader = styled.div`
+ border: 2px solid #d8edf6;
+ border-top: 2px solid transparent;
+ border-radius: 50%;
+ width: 25px;
+ height: 25px;
+ animation: ${spin} 1s linear infinite;
+ opacity: ${props => props.show ? 1 : 0}
+ margin: 20px auto 0;
+`
+
+export default Loader
diff --git a/components/loading.test.js b/components/loading.test.js
new file mode 100644
index 0000000..8c13d55
--- /dev/null
+++ b/components/loading.test.js
@@ -0,0 +1,10 @@
+import React from 'react'
+import Loading from './loading'
+import renderer from 'react-test-renderer'
+
+it('renders correctly', () => {
+ const tree = renderer.create(
+
+ ).toJSON()
+ expect(tree).toMatchSnapshot()
+})
diff --git a/components/logo.js b/components/logo.js
new file mode 100644
index 0000000..bfcbc2a
--- /dev/null
+++ b/components/logo.js
@@ -0,0 +1,8 @@
+const StyledImage = styled.img`
+ height: ${props => props.big ? '75px' : '50px'}
+ transition: height 1s;
+`
+
+const Logo = (props) =>
+
+export default Logo
diff --git a/components/logo.test.js b/components/logo.test.js
new file mode 100644
index 0000000..c32616b
--- /dev/null
+++ b/components/logo.test.js
@@ -0,0 +1,17 @@
+import React from 'react'
+import Logo from './logo'
+import renderer from 'react-test-renderer'
+
+it('renders big version', () => {
+ const tree = renderer.create(
+
+ ).toJSON()
+ expect(tree).toMatchSnapshot()
+})
+
+it('renders small version', () => {
+ const tree = renderer.create(
+
+ ).toJSON()
+ expect(tree).toMatchSnapshot()
+})
diff --git a/components/module.js b/components/module.js
new file mode 100644
index 0000000..130e7f7
--- /dev/null
+++ b/components/module.js
@@ -0,0 +1,26 @@
+import Card from './card'
+import Link from './link'
+import Heavy from './heavy'
+import Score from './score'
+import Tips from './tips'
+
+const GridItem = styled(Card)`
+ width: 300px;
+ min-height: 150px;
+ position: relative;
+ @media (max-width: 720px) {
+ width: 100%;
+ }
+ border: ${props => props.valid ? 'none' : '1px solid #c0392b'}
+ line-height: 1.5;
+`
+
+const Module = ({module}) =>
+
+ {module.name}
+
+
+
+
+
+export default Module
diff --git a/components/module.test.js b/components/module.test.js
new file mode 100644
index 0000000..d1fa232
--- /dev/null
+++ b/components/module.test.js
@@ -0,0 +1,31 @@
+import React from 'react'
+import Module from './module'
+import renderer from 'react-test-renderer'
+
+it('renders valid module', () => {
+ let module = {
+ name: 'module',
+ valid: true,
+ errors: [],
+ warnings: [],
+ suggestions: []
+ }
+ const tree = renderer.create(
+
+ ).toJSON()
+ expect(tree).toMatchSnapshot()
+})
+
+it('renders invalid module', () => {
+ let module = {
+ name: 'module',
+ valid: false,
+ errors: ['name is not allowed'],
+ warnings: [],
+ suggestions: []
+ }
+ const tree = renderer.create(
+
+ ).toJSON()
+ expect(tree).toMatchSnapshot()
+})
diff --git a/components/score.js b/components/score.js
new file mode 100644
index 0000000..2e11bb0
--- /dev/null
+++ b/components/score.js
@@ -0,0 +1,30 @@
+const width = 300 + 40 // width + padding
+
+const Bar = styled.div`
+ height: 5px;
+ width: 100%;
+ background: #EEE;
+
+ position: absolute;
+ bottom: 0;
+ left: 0;
+
+
+ box-sizing: border-box;
+ border-left: ${props => props.score/10 * width}px solid;
+ border-left: ${props => props.score === 0 ? width : ''}px solid;
+
+ border-color: ${props => props.score > 6 ? '#27ae60': '#f39c12'};
+ border-color: ${props => props.score === 0 ? '#c0392b': ''};
+
+ border-radius: 0 0 2px 2px;
+`
+
+const Score = ({module}) => {
+ let score = 10
+ score -= module.warnings.length
+ if (module.errors.length) score = 0
+ return
+}
+
+export default Score
diff --git a/components/score.test.js b/components/score.test.js
new file mode 100644
index 0000000..e321c87
--- /dev/null
+++ b/components/score.test.js
@@ -0,0 +1,36 @@
+import React from 'react'
+import Score from './score'
+import renderer from 'react-test-renderer'
+
+it('renders correctly when module has errors', () => {
+ let module = {
+ errors: ['invalid name'],
+ warnings: []
+ }
+ const tree = renderer.create(
+
+ ).toJSON()
+ expect(tree).toMatchSnapshot()
+})
+
+it('renders correctly when score > 6 ', () => {
+ let module = {
+ errors: [],
+ warnings: []
+ }
+ const tree = renderer.create(
+
+ ).toJSON()
+ expect(tree).toMatchSnapshot()
+})
+
+it('renders correctly when score <= 6 ', () => {
+ let module = {
+ errors: [],
+ warnings: ['warning 1', 'warning 2', 'warning 3', 'warning 4']
+ }
+ const tree = renderer.create(
+
+ ).toJSON()
+ expect(tree).toMatchSnapshot()
+})
diff --git a/components/tips.js b/components/tips.js
new file mode 100644
index 0000000..eb037e9
--- /dev/null
+++ b/components/tips.js
@@ -0,0 +1,40 @@
+import pluralize from 'pluralize'
+import Code from './code'
+
+const getTipsBlock = tips => {
+ return
+ {
+ tips
+ .map(tip => tip.replace('Missing recommended field: ', ''))
+ .map(tip => tip.replace('Missing optional field: ', ''))
+ .join(', ')
+ }
+
+
+}
+
+const Tips = ({module}) =>
+
+ {module.errors.length ?
+
{module.errors.length} {pluralize('error', module.errors.length)}
+ {module.errors.map((error, index) =>
- {error}
)}
+
+
: ''}
+
+ {module.warnings.length ?
+ Add recommended {pluralize('field', module.warnings.length)}: { }
+ {getTipsBlock(module.warnings)}
+
: ''}
+
+ {module.suggestions.length ?
+ Add optional {pluralize('field', module.suggestions.length)}: { }
+ {getTipsBlock(module.suggestions)}
+
: ''}
+
+ {!module.errors.length && !module.warnings.length && !module.suggestions.length ?
+ All good
💯
+
: ''}
+
+
+
+export default Tips
diff --git a/components/tips.test.js b/components/tips.test.js
new file mode 100644
index 0000000..06297f0
--- /dev/null
+++ b/components/tips.test.js
@@ -0,0 +1,15 @@
+import React from 'react'
+import Tips from './tips'
+import renderer from 'react-test-renderer'
+
+it('renders correctly', () => {
+ let module = {
+ errors: ['name is not valid'],
+ warnings: ['Missing recommended field: contributors'],
+ suggestions: ['Missing recommended field: engines']
+ }
+ const tree = renderer.create(
+
+ ).toJSON()
+ expect(tree).toMatchSnapshot()
+})
diff --git a/index.html b/index.html
deleted file mode 100644
index e6c1152..0000000
--- a/index.html
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
- npm-janitor: clean up all your package.json's
-
-
-
-
-
-
-
-
diff --git a/main.js b/main.js
deleted file mode 100644
index 0b0041d..0000000
--- a/main.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import React from 'react';
-import { Router, Route, Link } from 'react-router';
-import fetch from 'isomorphic-fetch';
-import Loader from 'react-loader';
-import GitHubForkRibbon from 'react-github-fork-ribbon';
-import Cards from './components/cards.jsx';
-import HotLink from './components/HotLink.jsx';
-import DebounceInput from 'react-debounce-input';
-
-class App extends React.Component {
- constructor (props) {
- super(props);
- this.state = {json: [], loaded: true};
- }
-
- _handleClick (name) {
- this.setState({loaded: false});
- this._fetch(name);
- }
-
- _fetch (name) {
- fetch(`https://npm-janitor.herokuapp.com/api/${name.toLowerCase()}`).
- then((data) => data.json()).
- then((json) => this.setState({json, loaded: true}));
- }
-
- render () {
- return
-
- Fork me on GitHub
-
-

-
-
npm-janitor
-
Enter the npm user name in the search box.
-
Red -> Error, Warnings -> Yellow and Green -> Recommendations.
-
-
-
-
-
- ;
- }
-}
-
-App.contextTypes = {
- router: React.PropTypes.func.isRequired
-};
-
-React.render((
-
-
-
-
-), document.querySelector('#content'));
diff --git a/npm-janitor.gif b/npm-janitor.gif
deleted file mode 100644
index b8cd060..0000000
Binary files a/npm-janitor.gif and /dev/null differ
diff --git a/npm-janitor/index.html b/npm-janitor/index.html
deleted file mode 100644
index e6c1152..0000000
--- a/npm-janitor/index.html
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
- npm-janitor: clean up all your package.json's
-
-
-
-
-
-
-
-
diff --git a/package.json b/package.json
index 7f65359..b33d7e4 100644
--- a/package.json
+++ b/package.json
@@ -1,31 +1,52 @@
{
"name": "npm-janitor-web",
- "private": "true",
- "browserify": {
- "transform": [
- "babelify"
- ]
- },
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
"scripts": {
- "build": "NODE_ENV=production browserify main.js | uglifyjs -cm > npm-janitor/bundle.js",
- "start": "ecstatic -p 8000 npm-janitor",
- "watch": "watchify main.js -o npm-janitor/bundle.js -dv"
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "test": "eslint . && jest --coverage"
+ },
+ "engines": {
+ "node": ">=7.6.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/npm-janitor/npm-janitor-web.git"
},
+ "keywords": [],
+ "author": "siddharthkp",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/npm-janitor/npm-janitor-web/issues"
+ },
+ "homepage": "https://github.com/npm-janitor/npm-janitor-web#readme",
"dependencies": {
- "babelify": "^6.1.3",
- "browserify": "^10.2.6",
- "ecstatic": "~0.8.0",
- "history": "^1.12.1",
- "isomorphic-fetch": "^2.1.1",
- "react": "~0.13.3",
- "react-debounce-input": "^1.1.3",
- "react-github-fork-ribbon": "^0.4.1",
- "react-loader": "^1.4.0",
- "react-masonry-component": "^1.0.4",
- "react-material-card": "0.0.2",
- "react-router": "^1.0.0-rc1",
- "uglify-js": "^2.4.24",
- "watchify": "^3.2.3",
- "whatwg-fetch": "^0.9.0"
+ "axios": "0.15.3",
+ "next": "2.0.0-beta.32",
+ "pluralize": "3.1.0",
+ "react": "15.4.2",
+ "react-dom": "15.4.2",
+ "styled-components": "1.4.3"
+ },
+ "devDependencies": {
+ "babel-eslint": "7.1.1",
+ "babel-jest": "19.0.0",
+ "babel-plugin-styled-components-require": "1.0.0",
+ "eslint": "3.16.0",
+ "jest": "19.0.0"
+ },
+ "jest": {
+ "collectCoverageFrom": [
+ "components/**/*.js",
+ "pages/**/*.js"
+ ],
+ "coveragePathIgnorePatterns": [
+ "/pages/_document.js",
+ "/pages/_global.js"
+ ],
+ "coverageDirectory": "coverage"
}
}
diff --git a/pages/__snapshots__/index.test.js.snap b/pages/__snapshots__/index.test.js.snap
new file mode 100644
index 0000000..e83a552
--- /dev/null
+++ b/pages/__snapshots__/index.test.js.snap
@@ -0,0 +1,35 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+
+
+

+
+
+
+
+`;
diff --git a/pages/__snapshots__/user.test.js.snap b/pages/__snapshots__/user.test.js.snap
new file mode 100644
index 0000000..486bea2
--- /dev/null
+++ b/pages/__snapshots__/user.test.js.snap
@@ -0,0 +1,131 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders with modules 1`] = `
+
+
+
+

+
+
+
+
+
+
+
+ npm-janitor-web
+
+
+
+
+
+ Add recommended
+ field
+ :
+
+
+ Add recommended field: contributors
+
+
+
+
+
+
+ Add optional
+ field
+ :
+
+
+ Add optional field: homepage
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`renders without modules 1`] = `
+
+
+
+

+
+
+
+
+

+
+
+`;
diff --git a/pages/_document.js b/pages/_document.js
new file mode 100644
index 0000000..2ce9c24
--- /dev/null
+++ b/pages/_document.js
@@ -0,0 +1,36 @@
+import Document, {Head, Main, NextScript} from 'next/document'
+import styleSheet from 'styled-components/lib/models/StyleSheet'
+import {injectGlobal} from 'styled-components'
+import React from 'react'
+import {styles, Head as _Head} from './_global'
+
+let stylesInjected = false
+
+export default class MyDocument extends Document {
+ static async getInitialProps ({renderPage}) {
+ if (!stylesInjected) {
+ injectGlobal`${styles}`
+ stylesInjected = true
+ }
+ const page = renderPage()
+
+ const style = styleSheet.rules().map(rule => rule.cssText).join('\n')
+
+ return {...page, style}
+ }
+
+ render () {
+ return (
+
+ <_Head/>
+
+
+
+
+
+
+
+
+ )
+ }
+}
diff --git a/pages/_global.js b/pages/_global.js
new file mode 100644
index 0000000..4aa348b
--- /dev/null
+++ b/pages/_global.js
@@ -0,0 +1,23 @@
+/* Hot reload of these styles are a bit flaky */
+
+const styles = `
+html, body {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ background: #F8F9FA;
+ font-size: 16px;
+ font-family: 'Lato', sans-serif;
+}
+`
+
+const Head = () => (
+
+ npm janitor
+
+
+
+)
+
+module.exports = {styles, Head}
diff --git a/pages/index.js b/pages/index.js
new file mode 100644
index 0000000..b471116
--- /dev/null
+++ b/pages/index.js
@@ -0,0 +1,7 @@
+import Header from '../components/header'
+
+const Home = () =>
+
+
+
+export default Home
diff --git a/pages/index.test.js b/pages/index.test.js
new file mode 100644
index 0000000..b70c8b5
--- /dev/null
+++ b/pages/index.test.js
@@ -0,0 +1,10 @@
+import React from 'react'
+import Index from './index'
+import renderer from 'react-test-renderer'
+
+it('renders correctly', () => {
+ const tree = renderer.create(
+
+ ).toJSON()
+ expect(tree).toMatchSnapshot()
+})
diff --git a/pages/user.js b/pages/user.js
new file mode 100644
index 0000000..1ffd2b6
--- /dev/null
+++ b/pages/user.js
@@ -0,0 +1,38 @@
+import Header from '../components/header'
+import Module from '../components/module'
+import getModules from '../api/user'
+import Grid from '../components/grid'
+
+const User = class extends React.Component {
+ constructor (props) {
+ super(props)
+ this.state = {fullHeader: true, user: props.url.query.name}
+ }
+
+ static async getInitialProps (context) {
+ let user = context.query.name
+ return getModules(user)
+ }
+
+ componentDidMount () {
+ this.setState({fullHeader: false, loading: false})
+ }
+
+ render() {
+ return
+
+
+ {this.props.modules.map((module, index) => {
+ return
+ })}
+ {!this.props.modules.length ?
: ''}
+
+
+ }
+}
+
+export default User
diff --git a/pages/user.test.js b/pages/user.test.js
new file mode 100644
index 0000000..a891f6c
--- /dev/null
+++ b/pages/user.test.js
@@ -0,0 +1,29 @@
+import React from 'react'
+import User from './user'
+import renderer from 'react-test-renderer'
+
+jest.mock('../api/user')
+
+it('renders with modules', () => {
+ let url = {query: {name: 'siddharthkp'}}
+ return User.getInitialProps(url)
+ .then(props => {
+ Object.assign(props, {url})
+ const tree = renderer.create(
+
+ ).toJSON()
+ expect(tree).toMatchSnapshot()
+ })
+})
+
+it('renders without modules', () => {
+ let url = {query: {name: ''}}
+ return User.getInitialProps(url)
+ .then(props => {
+ Object.assign(props, {url})
+ const tree = renderer.create(
+
+ ).toJSON()
+ expect(tree).toMatchSnapshot()
+ })
+})
diff --git a/readme.markdown b/readme.markdown
deleted file mode 100644
index b43f68a..0000000
--- a/readme.markdown
+++ /dev/null
@@ -1,30 +0,0 @@
-# npm-janitor-web
-> Web interface.
-
-
-
-# quick start
-
-```
-$ npm run watch &
-$ npm start
-```
-
-# commands
-
-* `npm run build` - build for production
-* `npm run watch` - automatically recompile during development
-* `npm start` - start a static development web server
-
-
-# TODO
-
-* Break the UI into more components.
-
-* Add a legend for colors.
-
-* Some animations?
-
-* Add a login with github.
-
-* Add a send issue button.
diff --git a/static/logo.png b/static/logo.png
new file mode 100644
index 0000000..9b02db8
Binary files /dev/null and b/static/logo.png differ
diff --git a/static/nothing.png b/static/nothing.png
new file mode 100644
index 0000000..c94b4a5
Binary files /dev/null and b/static/nothing.png differ