├── .gitignore
├── LICENSE.MD
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
├── renovate.json
├── src
├── App.css
├── App.js
├── App.test.js
├── common
│ ├── RouteUtil.jsx
│ └── utils.js
├── components
│ ├── Header.js
│ ├── Tweet.js
│ └── TweetItem.js
├── constant.js
├── graphql
│ ├── mutation.js
│ ├── query.js
│ ├── resolver.js
│ ├── subscription.js
│ └── typeDefs.js
├── images
│ └── twister.png
├── index.css
├── index.js
├── logo.svg
├── registerServiceWorker.js
├── router
│ └── router.js
├── screens
│ ├── HomePage.jsx
│ ├── LandingPage.jsx
│ ├── LoginPage.jsx
│ ├── LogoutPage.jsx
│ ├── SignupPage.jsx
│ └── style.js
└── styles
│ ├── main.css
│ ├── normalize.css
│ └── skeleton.css
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/LICENSE.MD:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Awesome GraphQL Space
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
React GraphQL Boilerplate
2 |
3 |
4 |
5 | 
6 |
7 | Bootstrap your fullstack GraphQL app within seconds
8 |
9 |
10 |
11 | ## Features
12 |
13 | - **Easily extensible**: A boilerplate only provides the minimum setup so you can tailor the API to your use case.
14 | - **Best practices**: Each boilerplate incorporates best practices from the GraphQL community.
15 |
16 |
17 | ## Project setup
18 | ```
19 | npm install
20 | ```
21 |
22 | ### Compiles and hot-reloads for development
23 | ```
24 | npm run start
25 | ```
26 |
27 | ## License
28 |
29 | MIT
30 |
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-app-manjula",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "apollo-boost": "^0.1.13",
7 | "apollo-cache-inmemory": "^1.2.6",
8 | "apollo-client": "^2.3.7",
9 | "apollo-link": "^1.2.2",
10 | "apollo-link-error": "^1.1.0",
11 | "apollo-link-http": "^1.5.4",
12 | "apollo-link-state": "^0.4.1",
13 | "apollo-link-ws": "^1.0.8",
14 | "graphql": "^0.13.2",
15 | "graphql-tag": "^2.9.2",
16 | "jwt-decode": "^2.2.0",
17 | "react": "^16.4.2",
18 | "react-apollo": "^2.1.9",
19 | "react-dom": "^16.4.2",
20 | "react-infinite-scroller": "^1.2.0",
21 | "react-onclickoutside": "^6.7.1",
22 | "react-router-dom": "^4.3.1",
23 | "react-scripts": "1.1.4",
24 | "styled-components": "^3.4.1",
25 | "subscriptions-transport-ws": "^0.9.14"
26 | },
27 | "scripts": {
28 | "start": "react-scripts start",
29 | "build": "react-scripts build",
30 | "test": "react-scripts test --env=jsdom",
31 | "eject": "react-scripts eject"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awesome-graphql-space/react-graphql/6041115e1e330c9d8b6f5115844c91d93145c890/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
26 | You need to enable JavaScript to run this app.
27 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: #f5f8fa
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App {
11 | text-align:center;
12 | }
13 |
14 |
15 | .App-header {
16 | background-color: #222;
17 | height: 80px;
18 | padding: 20px;
19 | color: white;
20 | }
21 |
22 | .App-title {
23 | font-size: 1.5em;
24 | }
25 |
26 | .App-intro {
27 | font-size: large;
28 | }
29 |
30 | @keyframes App-logo-spin {
31 | from { transform: rotate(0deg); }
32 | to { transform: rotate(360deg); }
33 | }
34 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react"
2 | import AppRouter from "./router/router"
3 | import { AUTH_TOKEN } from "./constant";
4 |
5 | class App extends Component {
6 | state = {
7 | canAccess: false
8 | }
9 |
10 | componentDidMount(){
11 | const app = localStorage.getItem(AUTH_TOKEN);
12 |
13 | if(app === null){
14 | this.setState({canAccess: false});
15 | }else if(app === ''){
16 | this.setState({canAccess: false});
17 | }else if(app.length > 0){
18 | this.setState({canAccess: true});
19 | }
20 | }
21 | render() {
22 | const { canAccess } = this.state;
23 | return (
24 |
25 | );
26 | }
27 | }
28 |
29 | export default App;
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render( , div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/common/RouteUtil.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import { Route, Redirect } from "react-router";
4 |
5 | /**
6 | * Component that protects route from unauthorized users.
7 | * @type {Object}
8 | */
9 | export class AuthRoute extends Component {
10 |
11 | static propTypes = {
12 | component: PropTypes.func,
13 | path: PropTypes.string,
14 | name: PropTypes.string,
15 | exact: PropTypes.bool,
16 | strict: PropTypes.bool
17 | };
18 |
19 | render() {
20 | let { canAccess, component, path, name, exact, strict } = this.props;
21 | let routeProps = {
22 | path,
23 | component,
24 | name,
25 | exact,
26 | strict
27 | };
28 |
29 | if (canAccess) {
30 | return ;
31 | } else {
32 | return ;
33 | }
34 | }
35 | }
36 |
37 | export class UnauthRoute extends Component {
38 | constructor(props) {
39 | super(props);
40 | }
41 |
42 | static propTypes = {
43 | component: PropTypes.func,
44 | path: PropTypes.string,
45 | name: PropTypes.string,
46 | exact: PropTypes.bool,
47 | strict: PropTypes.bool
48 | };
49 |
50 | render() {
51 | let { canAccess, component, path, name, exact, strict } = this.props;
52 | let routeProps = {
53 | path,
54 | component,
55 | name,
56 | exact,
57 | strict
58 | };
59 |
60 | if (canAccess) {
61 | return ;
62 | } else {
63 | return ;
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/common/utils.js:
--------------------------------------------------------------------------------
1 | import { AUTH_TOKEN } from "../constant";
2 |
3 | export const AuthUtil = {
4 |
5 | getToken(){
6 | localStorage.getItem(AUTH_TOKEN);
7 | },
8 |
9 | setToken(token){
10 | console.log(token);
11 | localStorage.setItem(AUTH_TOKEN, token);
12 | console.log('hello');
13 | }
14 |
15 | };
--------------------------------------------------------------------------------
/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import React from 'react';
3 | import {
4 | NavLink,
5 | Link,
6 | BrowserRouter as Router,
7 | Route,
8 | Switch,
9 | Redirect,
10 | } from 'react-router-dom'
11 | import { GET_AUTH_STATUS } from '../graphql/query';
12 | import { Query } from "react-apollo";
13 |
14 | const HeaderStyle = styled.div`
15 | background-color: #009CFA;
16 | height: 50px;
17 | padding: 20px;
18 | color: white;
19 | font-size: 24px;
20 | font-family: 'Open Sans',-apple-system,'BlinkMacSystemFont','Arial',sans-serif;
21 | `;
22 |
23 | const Twister = styled.div`
24 | float:left;
25 | font-size:30px;
26 | `
27 |
28 | const linkStyle ={
29 | color: "white",
30 | padding: "15px",
31 | float: "right"
32 | }
33 |
34 | const Header = () => {
35 | return(
36 |
37 |
38 | {({ loading, error, data, fetchMore }) => {
39 | if (loading) {
40 | return loading
;
41 | }
42 |
43 | if (error) {
44 | console.log(error)
45 | return error
;
46 | }
47 |
48 | console.log(data.getAuthStatus.loggedIn);
49 | return(
50 |
51 | {data.getAuthStatus.loggedIn ? Logout :
52 |
53 | Login
54 | Signup
55 | Twister
56 | }
57 |
58 | )
59 | }}
60 |
61 |
62 |
63 | )
64 | }
65 |
66 |
67 |
68 |
69 | export default Header
70 |
71 |
--------------------------------------------------------------------------------
/src/components/Tweet.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import onClickOutside from "react-onclickoutside";
4 | import { Mutation } from 'react-apollo';
5 | import { POST } from '../graphql/mutation';
6 | import { AuthUtil } from '../common/utils';
7 | import { AUTH_TOKEN } from '../constant';
8 | import {
9 | NavLink
10 | } from 'react-router-dom'
11 |
12 | const Button = styled.button`
13 | display: inline-block;
14 | border: 1px solid #1da1f2;
15 | color: #fff;
16 | border-radius: 100px;
17 | box-shadow: none;
18 | cursor: pointer;
19 | font-size: 14px;
20 | font-weight: bold;
21 | line-height: 20px;
22 | padding: 6px 16px;
23 | position: relative;
24 | text-align: center;
25 | white-space: nowrap;
26 | background-color: #EC4972;
27 | border-color: transparent;
28 | margin: 12px;
29 | font-size: 14px;
30 | font-weight: 400;
31 | text-align: center;
32 | white-space: nowrap;
33 | vertical-align: middle;
34 | cursor: pointer;
35 | border: 1px solid transparent;
36 | `
37 | const Section = styled.section`
38 | min-height: 20px;
39 | padding: 19px;
40 | margin-bottom: 20px;
41 | background-color: #f5f5f5;
42 | border: 1px solid #e3e3e3;
43 | border-radius: 4px;
44 | background-color: #E81C4F;
45 | background: rgba(232,28,79,0.1);
46 | `;
47 |
48 | export const Flex = styled.div`
49 | display: flex;
50 | flex: grow;
51 | justify-content: space-between;
52 | `;
53 |
54 | export const Row = styled.div`
55 | display: flex;
56 | justify-content: center;
57 | direction: row;
58 | padding:20px;
59 | `;
60 |
61 | const TextArea = styled.textarea`
62 | display: block;
63 | width: 100%;
64 | font-size: 14px;
65 | line-height: 1.42857143;
66 | color: #555;
67 | background-color: #fff;
68 | background-image: none;
69 | border: 1px solid #ccc;
70 | border-radius: 4px;
71 | border-color: #F5A4B8;
72 | box-shadow: 0 0 0 1px #F5A4B8;
73 | `
74 |
75 | class Tweet extends React.Component {
76 | constructor(props) {
77 | super(props);
78 | this.state = {
79 | text: '',
80 | remainingChar: 140,
81 | addPhotoStatus: false,
82 | showButtons: false
83 | }
84 | }
85 |
86 | handleChange(e) {
87 |
88 | this.setState({
89 | text: e.target.value,
90 | })
91 | }
92 |
93 | addPhoto(e) {
94 | this.setState({
95 | addPhotoStatus: !this.state.addPhotoStatus,
96 | });
97 | }
98 |
99 | remainingChar () {
100 | if (this.state.addPhotoStatus) {
101 | return 140 - 23 - (this.state.text.length);
102 | } else {
103 | return 140 - (this.state.text.length);
104 | }
105 | }
106 |
107 | overflowAlert () {
108 |
109 | if (this.remainingChar() < 0) {
110 | let beforeOverflowText;
111 | let overflowText;
112 |
113 | if (this.state.addPhotoStatus) {
114 | beforeOverflowText = this.state.text.substring(140 - 10 - 23, 140 -23);
115 | overflowText = this.state.text.substring(140 - 23);
116 | } else {
117 | beforeOverflowText = this.state.text.substring(140 - 10, 140);
118 | overflowText = this.state.text.substring(140);
119 | }
120 |
121 | return (
122 |
123 | // console.log('before: ' + beforeOverflowText);
124 |
125 | Oops! Too Long:
126 | ...{beforeOverflowText}
127 | {overflowText}
128 |
129 | );
130 | }
131 | }
132 |
133 | handleClickOutside(evt) {
134 | const { showButtons } = this.state;
135 |
136 | if(showButtons){
137 | this.setState({showButtons: false});
138 | }
139 | }
140 |
141 | render() {
142 | const { showButtons, text } = this.state;
143 |
144 | return (
145 |
146 |
147 | {(mutate, {loading, error}) => (
148 |
188 | )}
189 |
190 |
191 | );
192 | }
193 | }
194 |
195 | export default onClickOutside(Tweet);
196 |
197 | // {this.state.addPhotoStatus ? '✓ Photo Added' : 'Add Photo'}
--------------------------------------------------------------------------------
/src/components/TweetItem.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import styled from "styled-components";
4 | import { Flex, Row } from './Tweet';
5 |
6 | export const Card = styled.section`
7 | min-height: 20px;
8 | padding: 19px;
9 | border-radius:10px;
10 | margin-bottom: 20px;
11 | margin: 20px;
12 | background-color: #f5f5f5;
13 | border: 1px solid #e3e3e3;
14 | border-bottom: 1px solid #e6ecf0;
15 | cursor: pointer;
16 | min-height: 51px;
17 | padding: 9px 12px;
18 | background-color: #fff;
19 | &:hover {
20 | background-color: #f5f8fa;
21 | }
22 | `;
23 |
24 | export const Text = styled.p`
25 | text-align: left;
26 | padding: 5px;
27 | `;
28 |
29 | export const TweetItem = ({tweet}) => {
30 |
31 | // if(tweet){
32 | // return null;
33 | // }
34 |
35 | return(
36 |
37 |
38 | Posted by {tweet.author.displayName}
39 |
40 | {tweet.text}
41 |
42 |
43 | Like
44 | Reply
45 | comment
46 |
47 | Retwist
48 |
49 |
50 | );
51 | }
--------------------------------------------------------------------------------
/src/constant.js:
--------------------------------------------------------------------------------
1 | export const USER_ID = 'user-id'
2 | export const AUTH_TOKEN = 'AUTH_TOKEN'
--------------------------------------------------------------------------------
/src/graphql/mutation.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export const LOGIN = gql`
4 | mutation login($username: String!, $password: String!){
5 | login(username: $username, password: $password){
6 | token
7 | user {
8 | id
9 | username
10 | }
11 | }
12 | }
13 | `;
14 |
15 | export const LOGIN_USER = gql`
16 | mutation loginUser($isLoggedIn: Boolean!, $token: String!){
17 | loginUser(isLoggedIn: $isLoggedIn, token: $token) @client{
18 | id
19 | loggedIn
20 | token
21 | }
22 | }
23 | `;
24 |
25 | export const STORE_TOKEN = gql`
26 | mutation storeToken($loggedIn: Boolean!){
27 | storeToken(loggedIn: $loggedIn) @client
28 | }
29 | `;
30 |
31 | export const SIGNUP = gql`
32 | mutation signup($username: String!, $password: String!, $displayName: String!){
33 | signup(username: $username, password: $password, displayName: $displayName ){
34 | token
35 | user {
36 | id
37 | username
38 | displayName
39 | }
40 | }
41 | }
42 | `;
43 |
44 | /**
45 | * Post tweet mutation
46 | */
47 | export const POST = gql`
48 | mutation post($text: String!, $upload: String){
49 | post(text: $text, upload: $upload){
50 | id
51 | text
52 | }
53 | }
54 | `;
--------------------------------------------------------------------------------
/src/graphql/query.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | /**
4 | * tweets timeline query
5 | */
6 | export const TWEETS = gql`
7 | query {
8 | tweets {
9 | id
10 | text
11 | upload
12 | slug
13 | views
14 | author{
15 | id
16 | username
17 | displayName
18 | }
19 | }
20 | }
21 |
22 | `;
23 |
24 | /** Local state query */
25 | export const GET_AUTH_STATUS = gql`
26 | query getAuthStatus($id: String!) {
27 | getAuthStatus(id: $id) @client{
28 | id
29 | token
30 | loggedIn
31 | }
32 | }
33 | `;
34 |
35 |
36 | /** Local state query */
37 | export const GET_LOGIN_STATUS = gql`
38 | query {
39 | getLoginStatus @client
40 | }
41 | `;
--------------------------------------------------------------------------------
/src/graphql/resolver.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export const resolvers = {
4 | Mutation: {
5 | loginUser: async (_, { isLoggedIn, token }, { cache }) => {
6 | const query = gql`
7 | query {
8 | getAuthStatus( id: "1") @client{
9 | token
10 | id
11 | loggedIn
12 | }
13 | }
14 | `;
15 | const previous = cache.readQuery({ query });
16 |
17 | console.log(previous);
18 |
19 | const data = {
20 | ...previous,
21 | token,
22 | loggedIn: isLoggedIn
23 | };
24 |
25 | cache.writeData({id:`AuthPayload:${'1'}`, data });
26 | const current = await cache.readQuery({ query });
27 | console.log(current);
28 | return current.getAuthStatus;
29 | },
30 | }
31 | };
32 |
33 | export const defaults = {
34 | getAuthStatus :{
35 | __typename: 'AuthPayload',
36 | id: '1',
37 | loggedIn: false,
38 | token: ''
39 | },
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/src/graphql/subscription.js:
--------------------------------------------------------------------------------
1 | const TWEET_SUBSCRIPTION = gql`
2 | subscription tweetSubscription {
3 | tweetSubscription {
4 | node {
5 | id
6 | text
7 | upload
8 | slug
9 | views
10 | user {
11 | author
12 | }
13 | }
14 | }
15 | }
16 | `
--------------------------------------------------------------------------------
/src/graphql/typeDefs.js:
--------------------------------------------------------------------------------
1 | export const typeDefs = `
2 |
3 | type AuthPayload {
4 | token: String!
5 | id: String!
6 | loggedIn: Boolean!
7 | }
8 |
9 | type Mutation {
10 | loginUser(isLoggedIn: Boolean!, token: String!): AuthPayload
11 | # storeToken(isLoggedIn: Boolean!): Boolean
12 | }
13 |
14 | type Query {
15 | getAuthStatus(id: String!): AuthPayload
16 | # loggedIn: Boolean
17 | }
18 |
19 | `;
--------------------------------------------------------------------------------
/src/images/twister.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awesome-graphql-space/react-graphql/6041115e1e330c9d8b6f5115844c91d93145c890/src/images/twister.png
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import ApolloClient from "apollo-client";
4 | import { ApolloProvider } from "react-apollo";
5 | import { AUTH_TOKEN } from './constant'
6 | import { WebSocketLink } from 'apollo-link-ws'
7 | import { ApolloLink, split } from 'apollo-link'
8 | import { getMainDefinition } from 'apollo-utilities'
9 | import { HttpLink } from 'apollo-link-http'
10 | import { InMemoryCache } from 'apollo-cache-inmemory';
11 | import { withClientState } from "apollo-link-state";
12 | import registerServiceWorker from "./registerServiceWorker";
13 | import { resolvers, defaults } from './graphql/resolver';
14 | import { typeDefs } from './graphql/typeDefs';
15 | import App from "./App";
16 | import "./index.css";
17 | import "./styles/normalize.css";
18 | import "./styles/skeleton.css";
19 | import "./styles/main.css";
20 |
21 | const httpLink = new HttpLink({ uri: 'https://graphql-boilerplate-server-wqrtiyktjn.now.sh/graphql' })
22 |
23 | const middlewareLink = new ApolloLink((operation, forward) => {
24 | // get the authentication token from local storage if it exists
25 | const tokenValue = localStorage.getItem(AUTH_TOKEN)
26 | // return the headers to the context so httpLink can read them
27 | operation.setContext({
28 | headers: {
29 | Authorization: tokenValue ? `${tokenValue}` : '',
30 | },
31 | })
32 | return forward(operation)
33 | })
34 |
35 | // authenticated httplink
36 | const httpLinkAuth = middlewareLink.concat(httpLink)
37 |
38 | const wsLink = new WebSocketLink({
39 | uri: `ws://localhost:3000/graphql`,
40 | options: {
41 | reconnect: true,
42 | connectionParams: {
43 | Authorization: `Bearer ${localStorage.getItem(AUTH_TOKEN)}`,
44 | },
45 | },
46 | })
47 |
48 | const link = split(
49 | // split based on operation type
50 | ({ query }) => {
51 | const { kind, operation } = getMainDefinition(query)
52 | return kind === 'OperationDefinition' && operation === 'subscription'
53 | },
54 | wsLink,
55 | httpLinkAuth,
56 | )
57 |
58 | // Initialize apollo cache using inmemorycache
59 | const cache = new InMemoryCache();
60 |
61 | /**
62 | * @description Setting up apollo local state. It takse the default state, local resolvers
63 | * and typeDefs
64 | */
65 | const stateLink = withClientState({
66 | cache,
67 | defaults,
68 | resolvers,
69 | typeDefs
70 | });
71 |
72 | // apollo client setup
73 | const client = new ApolloClient({
74 | link: ApolloLink.from([stateLink, link]),
75 | cache,
76 | connectToDevTools: true,
77 | onError: (e) => { console.log(e.graphQLErrors) }
78 | })
79 |
80 | const token = localStorage.getItem(AUTH_TOKEN)
81 | // Render react app
82 | ReactDOM.render(
83 |
84 |
85 | , document.getElementById('root'));
86 |
87 | /**
88 | * Register react app PWA
89 | */
90 | registerServiceWorker();
91 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/router/router.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react"
2 | import { BrowserRouter as Router, Switch } from "react-router-dom";
3 | import LandingPage from "../screens/LandingPage";
4 | import LoginPage from "../screens/LoginPage";
5 | import SignupPage from "../screens/SignupPage";
6 | import HomePage from "../screens/HomePage";
7 | import decode from 'jwt-decode'
8 | import { AuthRoute, UnauthRoute } from "../common/RouteUtil";
9 | import LogoutPage from "../screens/LogoutPage";
10 |
11 | import "../App.css";
12 | import Header from "../components/Header";
13 | import { AUTH_TOKEN } from '../constant'
14 |
15 | class AppRouter extends Component {
16 |
17 | constructor(props) {
18 | super(props)
19 | this.refreshTokenFn = this.refreshTokenFn.bind(this)
20 |
21 | this.state = {
22 | token: props.token,
23 | }
24 | }
25 |
26 | refreshTokenFn(data = {}) {
27 | const token = data.AUTH_TOKEN
28 |
29 | if (token) {
30 | localStorage.setItem(AUTH_TOKEN, token)
31 | } else {
32 | localStorage.removeItem(AUTH_TOKEN)
33 | }
34 |
35 | this.setState({
36 | token: data.AUTH_TOKEN,
37 | })
38 | }
39 |
40 | //verify localStorage check
41 | componentDidMount() {
42 | this.bootStrapData()
43 | }
44 |
45 | bootStrapData() {
46 | try {
47 | const token = localStorage.getItem(AUTH_TOKEN)
48 | if (token !== null && token !== undefined) {
49 | const expired = this.isTokenExpired(token)
50 | if (!expired) {
51 | this.setState({ token: token })
52 | } else {
53 | localStorage.removeItem(AUTH_TOKEN)
54 | this.setState({ token: null })
55 | }
56 | }
57 | } catch (e) {
58 | console.log('')
59 | }
60 | }
61 |
62 | isTokenExpired(token) {
63 | const date = this.getTokenExpirationDate(token)
64 | const offsetSeconds = 0
65 | if (date === null) {
66 | return false
67 | }
68 | return !(date.valueOf() > new Date().valueOf() + offsetSeconds * 1000)
69 | }
70 |
71 | getTokenExpirationDate(token) {
72 | const decoded = decode(token)
73 | if (!decoded.exp) {
74 | return null
75 | }
76 | const date = new Date(0) // The 0 here is the key, which sets the date to the epoch
77 | date.setUTCSeconds(decoded.exp)
78 | return date
79 | }
80 |
81 | render() {
82 | const { canAccess } = this.props;
83 | return (
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | );
97 | }
98 | }
99 |
100 | export default AppRouter;
--------------------------------------------------------------------------------
/src/screens/HomePage.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Query } from "react-apollo";
3 | import InfiniteScroll from "react-infinite-scroller";
4 | import { TWEETS } from "../graphql/query";
5 | import { TweetItem } from "../components/TweetItem";
6 | import Tweet from "../components/Tweet";
7 |
8 | class HomePage extends Component {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 | {({ loading, error, data, fetchMore }) => {
18 | if (loading) {
19 | return loading
;
20 | }
21 |
22 | if (error) {
23 | return error
;
24 | }
25 |
26 | if (data.tweets === null || data.tweets.length < 1) {
27 | return empty
;
28 | }
29 |
30 | return (
31 |
36 | Loading ...
37 |
38 | }
39 | >
40 | {data.tweets.map(res => (
41 |
42 | ))}
43 |
44 | );
45 | }}
46 |
47 |
48 |
49 | );
50 | }
51 | }
52 |
53 | export default HomePage;
54 |
--------------------------------------------------------------------------------
/src/screens/LandingPage.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import styled from 'styled-components';
3 |
4 |
5 | const Header1 = styled.h1`
6 | margin: 50px;
7 | font-family: 'Open Sans',-apple-system,'BlinkMacSystemFont','Arial',sans-serif;
8 | `
9 |
10 | class LandingPage extends Component {
11 | render() {
12 | return (
13 |
14 | Login Or Signup to Twist your Tweet
15 |
16 | );
17 | }
18 | }
19 |
20 | export default LandingPage;
--------------------------------------------------------------------------------
/src/screens/LoginPage.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Mutation } from "react-apollo";
3 | import { NavLink } from "react-router-dom";
4 | import { LOGIN, LOGIN_USER } from "../graphql/mutation";
5 | import { AuthUtil } from "../common/utils";
6 | import { Title, Form, Button, Input } from "./style";
7 | import { AUTH_TOKEN } from "../constant";
8 |
9 | class LoginPage extends Component {
10 | state = {
11 | username: "",
12 | password: ""
13 | };
14 |
15 | render() {
16 | const { password, username } = this.state;
17 |
18 | return (
19 |
20 |
21 | {(mutate, { loading, error, client }) => (
22 |
23 |
Already have an account???
24 |
84 |
85 | )}
86 |
87 |
88 | );
89 | }
90 | }
91 |
92 | export default LoginPage;
93 |
--------------------------------------------------------------------------------
/src/screens/LogoutPage.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import styled from 'styled-components';
3 |
4 | class LogoutPage extends Component {
5 |
6 | componentDidMount(){
7 | localStorage.removeItem('AUTH_TOKEN');
8 | }
9 |
10 | render() {
11 | return (
12 |
13 |
You have been Logged out of the page
14 |
15 | );
16 | }
17 | }
18 |
19 | export default LogoutPage;
--------------------------------------------------------------------------------
/src/screens/SignupPage.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { NavLink } from "react-router-dom";
3 | import { Mutation } from "react-apollo";
4 | import { SIGNUP } from "../graphql/mutation";
5 | import { AuthUtil } from "../common/utils";
6 | import { Title, Form, Button, Input } from "./style";
7 |
8 | class SignupPage extends Component {
9 | state = {
10 | username: "",
11 | displayName: "",
12 | password: ""
13 | };
14 |
15 | render() {
16 | const { password, username, displayName } = this.state;
17 |
18 | return (
19 |
20 |
21 | {(mutate, { loading, error }) => (
22 |
23 |
Signup on twister
24 |
80 |
81 | )}
82 |
83 |
84 | );
85 | }
86 | }
87 |
88 | export default SignupPage;
89 |
--------------------------------------------------------------------------------
/src/screens/style.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | export const Title = styled.h2`
5 | margin-top: 40px;
6 | margin-bottom: 70px;
7 | font-size: 1.5em;
8 | color: black;
9 | `;
10 |
11 | export const Form = styled.form`
12 | margin-top: 40px;
13 | `;
14 |
15 | export const Button = styled.button`
16 | font-size: 14px;
17 | background-color: #009cfa;
18 | color: white;
19 | border-radius: 5px;
20 | padding: 0 10px;
21 | `;
22 |
23 | export const Input = styled.input`
24 | font-size: 14px;
25 | border: 1px solid #ddd;
26 | display: block;
27 | margin: 10px auto;
28 | border-radius: 5px;
29 | &::-webkit-input-placeholder {
30 | font-size: 14px;
31 | vertical-align: middle;
32 | }
33 | `;
34 |
--------------------------------------------------------------------------------
/src/styles/main.css:
--------------------------------------------------------------------------------
1 | button{
2 | color: #2792b3;
3 | }
--------------------------------------------------------------------------------
/src/styles/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */
2 |
3 | /**
4 | * 1. Set default font family to sans-serif.
5 | * 2. Prevent iOS text size adjust after orientation change, without disabling
6 | * user zoom.
7 | */
8 |
9 | html {
10 | font-family: sans-serif; /* 1 */
11 | -ms-text-size-adjust: 100%; /* 2 */
12 | -webkit-text-size-adjust: 100%; /* 2 */
13 | }
14 |
15 | /**
16 | * Remove default margin.
17 | */
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | /* HTML5 display definitions
24 | ========================================================================== */
25 |
26 | /**
27 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
29 | * and Firefox.
30 | * Correct `block` display not defined for `main` in IE 11.
31 | */
32 |
33 | article,
34 | aside,
35 | details,
36 | figcaption,
37 | figure,
38 | footer,
39 | header,
40 | hgroup,
41 | main,
42 | menu,
43 | nav,
44 | section,
45 | summary {
46 | display: block;
47 | }
48 |
49 | /**
50 | * 1. Correct `inline-block` display not defined in IE 8/9.
51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
52 | */
53 |
54 | audio,
55 | canvas,
56 | progress,
57 | video {
58 | display: inline-block; /* 1 */
59 | vertical-align: baseline; /* 2 */
60 | }
61 |
62 | /**
63 | * Prevent modern browsers from displaying `audio` without controls.
64 | * Remove excess height in iOS 5 devices.
65 | */
66 |
67 | audio:not([controls]) {
68 | display: none;
69 | height: 0;
70 | }
71 |
72 | /**
73 | * Address `[hidden]` styling not present in IE 8/9/10.
74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
75 | */
76 |
77 | [hidden],
78 | template {
79 | display: none;
80 | }
81 |
82 | /* Links
83 | ========================================================================== */
84 |
85 | /**
86 | * Remove the gray background color from active links in IE 10.
87 | */
88 |
89 | a {
90 | background-color: transparent;
91 | }
92 |
93 | /**
94 | * Improve readability when focused and also mouse hovered in all browsers.
95 | */
96 |
97 | a:active,
98 | a:hover {
99 | outline: 0;
100 | }
101 |
102 | /* Text-level semantics
103 | ========================================================================== */
104 |
105 | /**
106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
107 | */
108 |
109 | abbr[title] {
110 | border-bottom: 1px dotted;
111 | }
112 |
113 | /**
114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
115 | */
116 |
117 | b,
118 | strong {
119 | font-weight: bold;
120 | }
121 |
122 | /**
123 | * Address styling not present in Safari and Chrome.
124 | */
125 |
126 | dfn {
127 | font-style: italic;
128 | }
129 |
130 | /**
131 | * Address variable `h1` font-size and margin within `section` and `article`
132 | * contexts in Firefox 4+, Safari, and Chrome.
133 | */
134 |
135 | h1 {
136 | font-size: 2em;
137 | margin: 0.67em 0;
138 | }
139 |
140 | /**
141 | * Address styling not present in IE 8/9.
142 | */
143 |
144 | mark {
145 | background: #ff0;
146 | color: #000;
147 | }
148 |
149 | /**
150 | * Address inconsistent and variable font size in all browsers.
151 | */
152 |
153 | small {
154 | font-size: 80%;
155 | }
156 |
157 | /**
158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
159 | */
160 |
161 | sub,
162 | sup {
163 | font-size: 75%;
164 | line-height: 0;
165 | position: relative;
166 | vertical-align: baseline;
167 | }
168 |
169 | sup {
170 | top: -0.5em;
171 | }
172 |
173 | sub {
174 | bottom: -0.25em;
175 | }
176 |
177 | /* Embedded content
178 | ========================================================================== */
179 |
180 | /**
181 | * Remove border when inside `a` element in IE 8/9/10.
182 | */
183 |
184 | img {
185 | border: 0;
186 | }
187 |
188 | /**
189 | * Correct overflow not hidden in IE 9/10/11.
190 | */
191 |
192 | svg:not(:root) {
193 | overflow: hidden;
194 | }
195 |
196 | /* Grouping content
197 | ========================================================================== */
198 |
199 | /**
200 | * Address margin not present in IE 8/9 and Safari.
201 | */
202 |
203 | figure {
204 | margin: 1em 40px;
205 | }
206 |
207 | /**
208 | * Address differences between Firefox and other browsers.
209 | */
210 |
211 | hr {
212 | -moz-box-sizing: content-box;
213 | box-sizing: content-box;
214 | height: 0;
215 | }
216 |
217 | /**
218 | * Contain overflow in all browsers.
219 | */
220 |
221 | pre {
222 | overflow: auto;
223 | }
224 |
225 | /**
226 | * Address odd `em`-unit font size rendering in all browsers.
227 | */
228 |
229 | code,
230 | kbd,
231 | pre,
232 | samp {
233 | font-family: monospace, monospace;
234 | font-size: 1em;
235 | }
236 |
237 | /* Forms
238 | ========================================================================== */
239 |
240 | /**
241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
242 | * styling of `select`, unless a `border` property is set.
243 | */
244 |
245 | /**
246 | * 1. Correct color not being inherited.
247 | * Known issue: affects color of disabled elements.
248 | * 2. Correct font properties not being inherited.
249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
250 | */
251 |
252 | button,
253 | input,
254 | optgroup,
255 | select,
256 | textarea {
257 | color: inherit; /* 1 */
258 | font: inherit; /* 2 */
259 | margin: 0; /* 3 */
260 | }
261 |
262 | /**
263 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
264 | */
265 |
266 | button {
267 | overflow: visible;
268 | }
269 |
270 | /**
271 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
272 | * All other form control elements do not inherit `text-transform` values.
273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
274 | * Correct `select` style inheritance in Firefox.
275 | */
276 |
277 | button,
278 | select {
279 | text-transform: none;
280 | }
281 |
282 | /**
283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
284 | * and `video` controls.
285 | * 2. Correct inability to style clickable `input` types in iOS.
286 | * 3. Improve usability and consistency of cursor style between image-type
287 | * `input` and others.
288 | */
289 |
290 | button,
291 | html input[type="button"], /* 1 */
292 | input[type="reset"],
293 | input[type="submit"] {
294 | -webkit-appearance: button; /* 2 */
295 | cursor: pointer; /* 3 */
296 | }
297 |
298 | /**
299 | * Re-set default cursor for disabled elements.
300 | */
301 |
302 | button[disabled],
303 | html input[disabled] {
304 | cursor: default;
305 | }
306 |
307 | /**
308 | * Remove inner padding and border in Firefox 4+.
309 | */
310 |
311 | button::-moz-focus-inner,
312 | input::-moz-focus-inner {
313 | border: 0;
314 | padding: 0;
315 | }
316 |
317 | /**
318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
319 | * the UA stylesheet.
320 | */
321 |
322 | input {
323 | line-height: normal;
324 | }
325 |
326 | /**
327 | * It's recommended that you don't attempt to style these elements.
328 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
329 | *
330 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
331 | * 2. Remove excess padding in IE 8/9/10.
332 | */
333 |
334 | input[type="checkbox"],
335 | input[type="radio"] {
336 | box-sizing: border-box; /* 1 */
337 | padding: 0; /* 2 */
338 | }
339 |
340 | /**
341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
342 | * `font-size` values of the `input`, it causes the cursor style of the
343 | * decrement button to change from `default` to `text`.
344 | */
345 |
346 | input[type="number"]::-webkit-inner-spin-button,
347 | input[type="number"]::-webkit-outer-spin-button {
348 | height: auto;
349 | }
350 |
351 | /**
352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
354 | * (include `-moz` to future-proof).
355 | */
356 |
357 | input[type="search"] {
358 | -webkit-appearance: textfield; /* 1 */
359 | -moz-box-sizing: content-box;
360 | -webkit-box-sizing: content-box; /* 2 */
361 | box-sizing: content-box;
362 | }
363 |
364 | /**
365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
366 | * Safari (but not Chrome) clips the cancel button when the search input has
367 | * padding (and `textfield` appearance).
368 | */
369 |
370 | input[type="search"]::-webkit-search-cancel-button,
371 | input[type="search"]::-webkit-search-decoration {
372 | -webkit-appearance: none;
373 | }
374 |
375 | /**
376 | * Define consistent border, margin, and padding.
377 | */
378 |
379 | fieldset {
380 | border: 1px solid #c0c0c0;
381 | margin: 0 2px;
382 | padding: 0.35em 0.625em 0.75em;
383 | }
384 |
385 | /**
386 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
388 | */
389 |
390 | legend {
391 | border: 0; /* 1 */
392 | padding: 0; /* 2 */
393 | }
394 |
395 | /**
396 | * Remove default vertical scrollbar in IE 8/9/10/11.
397 | */
398 |
399 | textarea {
400 | overflow: auto;
401 | }
402 |
403 | /**
404 | * Don't inherit the `font-weight` (applied by a rule above).
405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
406 | */
407 |
408 | optgroup {
409 | font-weight: bold;
410 | }
411 |
412 | /* Tables
413 | ========================================================================== */
414 |
415 | /**
416 | * Remove most spacing between table cells.
417 | */
418 |
419 | table {
420 | border-collapse: collapse;
421 | border-spacing: 0;
422 | }
423 |
424 | td,
425 | th {
426 | padding: 0;
427 | }
--------------------------------------------------------------------------------
/src/styles/skeleton.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Skeleton V2.0.4
3 | * Copyright 2014, Dave Gamache
4 | * www.getskeleton.com
5 | * Free to use under the MIT license.
6 | * http://www.opensource.org/licenses/mit-license.php
7 | * 12/29/2014
8 | */
9 |
10 |
11 | /* Table of contents
12 | ––––––––––––––––––––––––––––––––––––––––––––––––––
13 | - Grid
14 | - Base Styles
15 | - Typography
16 | - Links
17 | - Buttons
18 | - Forms
19 | - Lists
20 | - Code
21 | - Tables
22 | - Spacing
23 | - Utilities
24 | - Clearing
25 | - Media Queries
26 | */
27 |
28 |
29 | /* Grid
30 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
31 | .container {
32 | position: relative;
33 | width: 100%;
34 | max-width: 960px;
35 | margin: 0 auto;
36 | padding: 0 20px;
37 | box-sizing: border-box; }
38 | .column,
39 | .columns {
40 | width: 100%;
41 | float: left;
42 | box-sizing: border-box; }
43 |
44 | /* For devices larger than 400px */
45 | @media (min-width: 400px) {
46 | .container {
47 | width: 85%;
48 | padding: 0; }
49 | }
50 |
51 | /* For devices larger than 550px */
52 | @media (min-width: 550px) {
53 | .container {
54 | width: 80%; }
55 | .column,
56 | .columns {
57 | margin-left: 4%; }
58 | .column:first-child,
59 | .columns:first-child {
60 | margin-left: 0; }
61 |
62 | .one.column,
63 | .one.columns { width: 4.66666666667%; }
64 | .two.columns { width: 13.3333333333%; }
65 | .three.columns { width: 22%; }
66 | .four.columns { width: 30.6666666667%; }
67 | .five.columns { width: 39.3333333333%; }
68 | .six.columns { width: 48%; }
69 | .seven.columns { width: 56.6666666667%; }
70 | .eight.columns { width: 65.3333333333%; }
71 | .nine.columns { width: 74.0%; }
72 | .ten.columns { width: 82.6666666667%; }
73 | .eleven.columns { width: 91.3333333333%; }
74 | .twelve.columns { width: 100%; margin-left: 0; }
75 |
76 | .one-third.column { width: 30.6666666667%; }
77 | .two-thirds.column { width: 65.3333333333%; }
78 |
79 | .one-half.column { width: 48%; }
80 |
81 | /* Offsets */
82 | .offset-by-one.column,
83 | .offset-by-one.columns { margin-left: 8.66666666667%; }
84 | .offset-by-two.column,
85 | .offset-by-two.columns { margin-left: 17.3333333333%; }
86 | .offset-by-three.column,
87 | .offset-by-three.columns { margin-left: 26%; }
88 | .offset-by-four.column,
89 | .offset-by-four.columns { margin-left: 34.6666666667%; }
90 | .offset-by-five.column,
91 | .offset-by-five.columns { margin-left: 43.3333333333%; }
92 | .offset-by-six.column,
93 | .offset-by-six.columns { margin-left: 52%; }
94 | .offset-by-seven.column,
95 | .offset-by-seven.columns { margin-left: 60.6666666667%; }
96 | .offset-by-eight.column,
97 | .offset-by-eight.columns { margin-left: 69.3333333333%; }
98 | .offset-by-nine.column,
99 | .offset-by-nine.columns { margin-left: 78.0%; }
100 | .offset-by-ten.column,
101 | .offset-by-ten.columns { margin-left: 86.6666666667%; }
102 | .offset-by-eleven.column,
103 | .offset-by-eleven.columns { margin-left: 95.3333333333%; }
104 |
105 | .offset-by-one-third.column,
106 | .offset-by-one-third.columns { margin-left: 34.6666666667%; }
107 | .offset-by-two-thirds.column,
108 | .offset-by-two-thirds.columns { margin-left: 69.3333333333%; }
109 |
110 | .offset-by-one-half.column,
111 | .offset-by-one-half.columns { margin-left: 52%; }
112 |
113 | }
114 |
115 |
116 | /* Base Styles
117 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
118 | /* NOTE
119 | html is set to 62.5% so that all the REM measurements throughout Skeleton
120 | are based on 10px sizing. So basically 1.5rem = 15px :) */
121 | html {
122 | font-size: 62.5%; }
123 | body {
124 | font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
125 | line-height: 1.6;
126 | font-weight: 400;
127 | font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
128 | color: #222; }
129 |
130 |
131 | /* Typography
132 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
133 | h1, h2, h3, h4, h5, h6 {
134 | margin-top: 0;
135 | margin-bottom: 2rem;
136 | font-weight: 300; }
137 | h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;}
138 | h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; }
139 | h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; }
140 | h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; }
141 | h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; }
142 | h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; }
143 |
144 | /* Larger than phablet */
145 | @media (min-width: 550px) {
146 | h1 { font-size: 5.0rem; }
147 | h2 { font-size: 4.2rem; }
148 | h3 { font-size: 3.6rem; }
149 | h4 { font-size: 3.0rem; }
150 | h5 { font-size: 2.4rem; }
151 | h6 { font-size: 1.5rem; }
152 | }
153 |
154 | p {
155 | margin-top: 0; }
156 |
157 |
158 | /* Links
159 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
160 | a {
161 | color: #1EAEDB; }
162 | a:hover {
163 | color: #0FA0CE; }
164 |
165 |
166 | /* Buttons
167 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
168 | .button,
169 | button,
170 | input[type="submit"],
171 | input[type="reset"],
172 | input[type="button"] {
173 | display: inline-block;
174 | height: 38px;
175 | padding: 0 30px;
176 | color: #555;
177 | text-align: center;
178 | font-size: 11px;
179 | font-weight: 600;
180 | line-height: 38px;
181 | letter-spacing: .1rem;
182 | text-transform: uppercase;
183 | text-decoration: none;
184 | white-space: nowrap;
185 | background-color: transparent;
186 | border-radius: 4px;
187 | border: 1px solid #bbb;
188 | cursor: pointer;
189 | box-sizing: border-box; }
190 | .button:hover,
191 | button:hover,
192 | input[type="submit"]:hover,
193 | input[type="reset"]:hover,
194 | input[type="button"]:hover,
195 | .button:focus,
196 | button:focus,
197 | input[type="submit"]:focus,
198 | input[type="reset"]:focus,
199 | input[type="button"]:focus {
200 | color: #333;
201 | border-color: #888;
202 | outline: 0; }
203 | .button.button-primary,
204 | button.button-primary,
205 | input[type="submit"].button-primary,
206 | input[type="reset"].button-primary,
207 | input[type="button"].button-primary {
208 | color: #FFF;
209 | background-color: #33C3F0;
210 | border-color: #33C3F0; }
211 | .button.button-primary:hover,
212 | button.button-primary:hover,
213 | input[type="submit"].button-primary:hover,
214 | input[type="reset"].button-primary:hover,
215 | input[type="button"].button-primary:hover,
216 | .button.button-primary:focus,
217 | button.button-primary:focus,
218 | input[type="submit"].button-primary:focus,
219 | input[type="reset"].button-primary:focus,
220 | input[type="button"].button-primary:focus {
221 | color: #FFF;
222 | background-color: #1EAEDB;
223 | border-color: #1EAEDB; }
224 |
225 |
226 | /* Forms
227 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
228 | input[type="email"],
229 | input[type="number"],
230 | input[type="search"],
231 | input[type="text"],
232 | input[type="tel"],
233 | input[type="url"],
234 | input[type="password"],
235 | textarea,
236 | select {
237 | height: 38px;
238 | padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
239 | background-color: #fff;
240 | border: 1px solid #D1D1D1;
241 | border-radius: 4px;
242 | box-shadow: none;
243 | box-sizing: border-box; }
244 | /* Removes awkward default styles on some inputs for iOS */
245 | input[type="email"],
246 | input[type="number"],
247 | input[type="search"],
248 | input[type="text"],
249 | input[type="tel"],
250 | input[type="url"],
251 | input[type="password"],
252 | textarea {
253 | -webkit-appearance: none;
254 | -moz-appearance: none;
255 | appearance: none; }
256 | textarea {
257 | min-height: 65px;
258 | padding-top: 6px;
259 | padding-bottom: 6px; }
260 | input[type="email"]:focus,
261 | input[type="number"]:focus,
262 | input[type="search"]:focus,
263 | input[type="text"]:focus,
264 | input[type="tel"]:focus,
265 | input[type="url"]:focus,
266 | input[type="password"]:focus,
267 | textarea:focus,
268 | select:focus {
269 | border: 1px solid #33C3F0;
270 | outline: 0; }
271 | label,
272 | legend {
273 | display: block;
274 | margin-bottom: .5rem;
275 | font-weight: 600; }
276 | fieldset {
277 | padding: 0;
278 | border-width: 0; }
279 | input[type="checkbox"],
280 | input[type="radio"] {
281 | display: inline; }
282 | label > .label-body {
283 | display: inline-block;
284 | margin-left: .5rem;
285 | font-weight: normal; }
286 |
287 |
288 | /* Lists
289 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
290 | ul {
291 | list-style: circle inside; }
292 | ol {
293 | list-style: decimal inside; }
294 | ol, ul {
295 | padding-left: 0;
296 | margin-top: 0; }
297 | ul ul,
298 | ul ol,
299 | ol ol,
300 | ol ul {
301 | margin: 1.5rem 0 1.5rem 3rem;
302 | font-size: 90%; }
303 | li {
304 | margin-bottom: 1rem; }
305 |
306 |
307 | /* Code
308 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
309 | code {
310 | padding: .2rem .5rem;
311 | margin: 0 .2rem;
312 | font-size: 90%;
313 | white-space: nowrap;
314 | background: #F1F1F1;
315 | border: 1px solid #E1E1E1;
316 | border-radius: 4px; }
317 | pre > code {
318 | display: block;
319 | padding: 1rem 1.5rem;
320 | white-space: pre; }
321 |
322 |
323 | /* Tables
324 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
325 | th,
326 | td {
327 | padding: 12px 15px;
328 | text-align: left;
329 | border-bottom: 1px solid #E1E1E1; }
330 | th:first-child,
331 | td:first-child {
332 | padding-left: 0; }
333 | th:last-child,
334 | td:last-child {
335 | padding-right: 0; }
336 |
337 |
338 | /* Spacing
339 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
340 | button,
341 | .button {
342 | margin-bottom: 1rem; }
343 | input,
344 | textarea,
345 | select,
346 | fieldset {
347 | margin-bottom: 1.5rem; }
348 | pre,
349 | blockquote,
350 | dl,
351 | figure,
352 | table,
353 | p,
354 | ul,
355 | ol,
356 | form {
357 | margin-bottom: 2.5rem; }
358 |
359 |
360 | /* Utilities
361 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
362 | .u-full-width {
363 | width: 100%;
364 | box-sizing: border-box; }
365 | .u-max-full-width {
366 | max-width: 100%;
367 | box-sizing: border-box; }
368 | .u-pull-right {
369 | float: right; }
370 | .u-pull-left {
371 | float: left; }
372 |
373 |
374 | /* Misc
375 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
376 | hr {
377 | margin-top: 3rem;
378 | margin-bottom: 3.5rem;
379 | border-width: 0;
380 | border-top: 1px solid #E1E1E1; }
381 |
382 |
383 | /* Clearing
384 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
385 |
386 | /* Self Clearing Goodness */
387 | .container:after,
388 | .row:after,
389 | .u-cf {
390 | content: "";
391 | display: table;
392 | clear: both; }
393 |
394 |
395 | /* Media Queries
396 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
397 | /*
398 | Note: The best way to structure the use of media queries is to create the queries
399 | near the relevant code. For example, if you wanted to change the styles for buttons
400 | on small devices, paste the mobile query code up in the buttons section and style it
401 | there.
402 | */
403 |
404 |
405 | /* Larger than mobile */
406 | @media (min-width: 400px) {}
407 |
408 | /* Larger than phablet (also point when grid becomes active) */
409 | @media (min-width: 550px) {}
410 |
411 | /* Larger than tablet */
412 | @media (min-width: 750px) {}
413 |
414 | /* Larger than desktop */
415 | @media (min-width: 1000px) {}
416 |
417 | /* Larger than Desktop HD */
418 | @media (min-width: 1200px) {}
419 |
--------------------------------------------------------------------------------