├── .babelrc
├── .eslintrc
├── .flowconfig
├── .gitignore
├── README.md
├── components
├── Container.js
├── Footer.js
├── Header.js
├── Page.js
├── UserThumb.js
└── index.js
├── layouts
├── Posts.js
├── Users.js
└── index.js
├── package.json
├── pages
├── _document.js
├── index.js
└── posts.js
├── utils
├── api.js
├── api.test.js
├── capitalizefl.js
├── capitalizefl.test.js
├── index.js
├── initials.js
├── initials.test.js
└── types.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "next/babel"
4 | ],
5 | "plugins": [
6 | "transform-export-extensions"
7 | ],
8 | "env": {
9 | "test": {
10 | "presets": ["es2015", "next/babel"]
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "env": {
4 | "browser": true,
5 | "node": true,
6 | "jest": true
7 | },
8 | "extends": [
9 | "airbnb",
10 | "plugin:flowtype/recommended"
11 | ],
12 | "plugins": [
13 | "flowtype"
14 | ],
15 | "rules": {
16 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
17 | "react/require-default-props": [0],
18 | "import/first": [0],
19 | "no-unused-expressions": [0],
20 | "arrow-parens": [0],
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renatorib/next-jph/706b44bee21529cf49a8fd2e44b2b01b0286f957/.flowconfig
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .next
3 | .DS_Store
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 |
3 |
4 |
5 | ### What?
6 | Next JPH is an example app made with [JsonPlaceholder](http://jsonplaceholder.typicode.com/) api, using [Next.js](http://github.com/zeit/next.js), [styled-components](http://github.com/styled-components/styled-components), [flow](http://github.com/facebook/flow) and [jest](http://github.com/facebook/jest).
7 |
8 | *"JPH" is an acronym to **J**son**P**lace**h**older.*
9 |
10 | **See the app running at `now`**: http://jph.now.sh
11 |
12 | ### Why?
13 | This is a project I've made to learn more about the used stack.
14 | Also, is a good reference to newcommers in these technologies.
15 |
16 | Feel free to contribute if you find any mistake I've made, or if you want to improve the application.
17 |
18 |
19 | ## Development
20 | First of all, install dependencies:
21 | ```sh
22 | yarn
23 | ```
24 |
25 | Run development server:
26 | ```sh
27 | yarn dev
28 | ```
29 | Then open http://localhost:3000
30 |
31 | ## Production Deployment
32 | Next.js is production-ready framework with *server-side rendering* and *code-splitting* built-in.
33 |
34 | Just run these commands:
35 | ```sh
36 | yarn build
37 | yarn start
38 | ```
39 |
--------------------------------------------------------------------------------
/components/Container.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export default styled.div`
4 | width: 100%;
5 | max-width: 580px;
6 | padding: 0 10px;
7 | margin: 0 auto;
8 | box-sizing: border-box;
9 | `;
10 |
--------------------------------------------------------------------------------
/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | export default () => (
5 |
6 |
7 | Next.js JsonPlaceholder sample app by
8 |
9 | @renatorib
10 |
11 |
12 |
13 | );
14 |
15 | const Wrapper = styled.div`
16 | margin-top: 30px;
17 | border-top: 1px solid #EEEEEE;
18 | `;
19 |
20 | const CenterMessage = styled.div`
21 | padding: 30px 0;
22 | text-align: center;
23 | color: #BBBBBB;
24 | font-size: 12px;
25 |
26 | a {
27 | color: #8888AA;
28 | text-decoration: none;
29 | }
30 | `;
31 |
--------------------------------------------------------------------------------
/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import Link from 'next/link';
4 | import { Container } from '.';
5 |
6 | export default () => (
7 |
8 |
9 |
10 |
11 |
12 | Next JPH
13 |
14 |
15 | Github
16 |
17 |
18 |
19 |
20 |
21 | );
22 |
23 | const Wrapper = styled.div`
24 | height: 50px;
25 | `;
26 |
27 | const Fixed = styled.div`
28 | width: 100%;
29 | height: 50px;
30 | line-height: 50px;
31 | border-bottom: 1px solid #EEE;
32 | background: white;
33 | position: fixed;
34 | z-index: 99;
35 | `;
36 |
37 | const Flex = styled.div`
38 | display: flex;
39 | align-items: center;
40 | justify-content: space-between;
41 | `;
42 |
43 | const Logo = styled.div`
44 | a {
45 | color: #888;
46 | font-weight: 300;
47 | letter-spacing: 1px;
48 | font-size: 22px;
49 | text-decoration: none;
50 | transition: all 1s ease;
51 |
52 | &:hover {
53 | color: #9b59b6;
54 | }
55 | }
56 | `;
57 |
58 | const Links = styled.div`
59 | a {
60 | color: #AAAAFF;
61 | transition: all 200ms ease;
62 | text-decoration: none;
63 | font-weight: 400;
64 |
65 | &:hover {
66 | opacity: 0.4;
67 | }
68 |
69 | &:not(:last-child) {
70 | margin-right: 10px;
71 | }
72 | }
73 | `;
74 |
--------------------------------------------------------------------------------
/components/Page.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import React from 'react';
4 | import Head from 'next/head';
5 | import NProgress from 'nprogress';
6 | import Router from 'next/router';
7 |
8 | Router.onRouteChangeStart = () => NProgress.start();
9 | Router.onRouteChangeComplete = () => NProgress.done();
10 | Router.onRouteChangeError = () => NProgress.done();
11 |
12 | type Props = {
13 | children?: any,
14 | title?: string,
15 | };
16 |
17 | const Page = ({ children, title, ...restProps }: Props) => (
18 |
19 |
20 |
{title || 'Placeholder'}
21 |
22 | {children}
23 |
24 | );
25 |
26 | export default Page;
27 |
--------------------------------------------------------------------------------
/components/UserThumb.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import React from 'react';
4 | import styled from 'styled-components';
5 | import { prop } from 'styled-tools';
6 | import { initials } from '../utils';
7 |
8 | type Props = {
9 | name: string,
10 | };
11 |
12 | export default ({ name, ...restProps }: Props) => (
13 |
14 | {initials(name)}
15 |
16 | );
17 |
18 | const Wrapper = styled.div`
19 | width: ${prop('size', 50)}px;
20 | height: ${prop('size', 50)}px;
21 | background: ${prop('color', '#aaaaaa')};
22 | color: white;
23 | border-radius: ${prop('size', 50)}px;
24 | font-size: 20px;
25 | line-height: ${prop('size', 50)}px;
26 | text-align: center;
27 | font-weight: 400;
28 | `;
29 |
--------------------------------------------------------------------------------
/components/index.js:
--------------------------------------------------------------------------------
1 | export Container from './Container';
2 | export Footer from './Footer';
3 | export Header from './Header';
4 | export Page from './Page';
5 | export UserThumb from './UserThumb';
6 |
--------------------------------------------------------------------------------
/layouts/Posts.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { Post, User } from '../utils/types';
4 | import React from 'react';
5 | import styled from 'styled-components';
6 | import { Header, Footer, Container } from '../components';
7 | import { capitalizefl } from '../utils';
8 |
9 | type Props = {
10 | posts: Post[],
11 | user: User,
12 | };
13 |
14 | export default ({ posts, user }: Props) => (
15 |
16 |
17 |
18 |
19 | posts from
20 | {user.name}
21 |
22 |
23 | {posts && posts.map(post => (
24 |
25 | {capitalizefl(post.title)}.
26 | {capitalizefl(post.body)}.
27 |
28 | ))}
29 |
30 |
31 |
32 | );
33 |
34 | const Wrapper = styled.div`
35 | background: #FFF;
36 | `;
37 |
38 | const Title = styled.div`
39 | margin-top: 60px;
40 | margin-bottom: 20px;
41 | text-align: center;
42 | position: relative;
43 |
44 | @media (min-width: 600px) {
45 | &:before, &:after {
46 | content: "";
47 | position: absolute;
48 | display: inline-block;
49 | vertical-align: middle;
50 | width: 46px;
51 | height: 1px;
52 | background: #eee;
53 | margin: 0 30px;
54 | left: 0;
55 | top: 50%;
56 | }
57 |
58 | &:after {
59 | left: auto;
60 | right: 0;
61 | }
62 | }
63 |
64 | span {
65 | font-size: 12px;
66 | color: #BBBBBB;
67 | font-style: italic;
68 | letter-spacing: 2px;
69 | }
70 |
71 | h1 {
72 | font-family: 'IM Fell Double Pica';
73 | color: #3498db;
74 | text-align: center;
75 | font-weight: italic;
76 | font-weight: normal;
77 | margin: 0;
78 | font-size: 38px;
79 | }
80 | `;
81 |
82 | const PostsContained = styled(Container)`
83 | width: 100%;
84 | display: flex;
85 | flex-direction: column;
86 | `;
87 |
88 | const PostContent = styled.div`
89 | margin-top: 30px;
90 | color: #888;
91 |
92 | h2 {
93 | color: #897492;
94 | font-family: 'Sansita';
95 | font-size: 30px;
96 | line-height: 1.1em;
97 | letter-spacing: 1px;
98 | }
99 |
100 | p {
101 | letter-spacing: 0.3px;
102 | line-height: 1.42em;
103 | font-size: 18px;
104 | color: #aaa;
105 | }
106 | `;
107 |
--------------------------------------------------------------------------------
/layouts/Users.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { User } from '../utils/types';
4 | import React from 'react';
5 | import styled from 'styled-components';
6 | import Link from 'next/link';
7 | import { Header, Footer, UserThumb, Container } from '../components';
8 |
9 | type Props = {
10 | users: User[],
11 | };
12 |
13 | export default ({ users }: Props) => (
14 |
15 |
16 |
17 | {users && users.map(user => (
18 |
19 |
20 |
21 |
22 | {user.name}
23 | Read posts from @{user.username.toLowerCase()}
24 |
25 |
26 |
27 | ))}
28 |
29 |
30 |
31 | );
32 |
33 | const Wrapper = styled.div`
34 | background: #FFF;
35 | `;
36 |
37 | const UsersContained = styled(Container)`
38 | width: 100%;
39 | display: flex;
40 | flex-direction: column;
41 | `;
42 |
43 | const UserCard = styled.div`
44 | width: 100%;
45 | height: auto;
46 | padding: 10px;
47 | border-radius: 5px;
48 | background: white;
49 | cursor: pointer;
50 | transition: all 400ms ease;
51 | margin-top: 20px;
52 | box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
53 |
54 | display: flex;
55 | align-items: center;
56 |
57 | &:hover {
58 | box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
59 | transform: translate(0, -2px);
60 | }
61 |
62 | > *:not(:last-child) {
63 | margin-right: 20px;
64 | }
65 | `;
66 |
67 | const UserContent = styled.div`
68 | h2 {
69 | margin: 0;
70 | font-family: 'Sansita';
71 | letter-spacing: 1px;
72 | color: #897492;
73 | font-size: 20px;
74 | }
75 |
76 | span {
77 | color: #bbb;
78 | font-size: 14px;
79 | letter-spacing: 0.6px;
80 | padding-top: 10px;
81 | }
82 | `;
83 |
--------------------------------------------------------------------------------
/layouts/index.js:
--------------------------------------------------------------------------------
1 | export Users from './Users';
2 | export Posts from './Posts';
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Placeholder",
3 | "scripts": {
4 | "dev": "next",
5 | "build": "next build",
6 | "start": "next start",
7 | "flow": "flow; test $? -eq 0 -o $? -eq 2",
8 | "test": "jest"
9 | },
10 | "dependencies": {
11 | "axios": "^0.15.3",
12 | "babel-jest": "^19.0.0",
13 | "babel-preset-es2015": "^6.24.0",
14 | "jest": "^19.0.2",
15 | "next": "beta",
16 | "nprogress": "^0.2.0",
17 | "react": "^15.4.2",
18 | "react-dom": "^15.4.2",
19 | "styled-components": "^1.4.4",
20 | "styled-tools": "^0.1.4"
21 | },
22 | "devDependencies": {
23 | "babel-eslint": "^7.1.1",
24 | "babel-plugin-transform-export-extensions": "^6.22.0",
25 | "eslint": "^3.17.1",
26 | "eslint-config-airbnb": "^14.1.0",
27 | "eslint-plugin-babel": "^4.1.1",
28 | "eslint-plugin-flowtype": "^2.30.3",
29 | "eslint-plugin-import": "^2.2.0",
30 | "eslint-plugin-jsx-a11y": "^4.0.0",
31 | "eslint-plugin-react": "^6.10.0",
32 | "flow-bin": "^0.41.0"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Document, { Head, Main, NextScript } from 'next/document';
3 | import { injectGlobal } from 'styled-components';
4 | import styleSheet from 'styled-components/lib/models/StyleSheet';
5 |
6 | injectGlobal`
7 | body, html {
8 | margin: 0;
9 | }
10 |
11 | body {
12 | font-family: "Roboto";
13 | font-weight: 300;
14 | }
15 |
16 | *, *:after, *:before {
17 | box-sizing: border-box;
18 | }
19 |
20 | #nprogress {
21 | pointer-events: none;
22 | }
23 |
24 | #nprogress .bar {
25 | background: #ff9300;
26 | position: fixed;
27 | z-index: 1031;
28 | top: 0;
29 | left: 0;
30 | width: 100%;
31 | height: 2px;
32 | }
33 |
34 | #nprogress .peg {
35 | display: block;
36 | position: absolute;
37 | right: 0px;
38 | width: 100px;
39 | height: 100%;
40 | box-shadow: 0 0 10px #ff9300, 0 0 5px #ff9300;
41 | opacity: 1.0;
42 | transform: rotate(3deg) translate(0px, -4px);
43 | }
44 | `;
45 |
46 | class MyDocument extends Document {
47 | static async getInitialProps({ renderPage }) {
48 | const page = renderPage();
49 | const styles = (
50 |