├── .all-contributorsrc
├── .babelrc
├── .codeclimate.yml
├── .ebextensions
└── config.config
├── .editorconfig
├── .env-sample
├── .eslintignore
├── .eslintrc
├── .flowconfig
├── .gitattributes
├── .gitignore
├── .graphqlconfig
├── .htaccess
├── .node-version
├── .npmignore
├── .nvmrc
├── .prettierrc
├── .travis.yml
├── .yarnclean
├── CODE_OF_CONDUCT.md
├── Graphcool-simplified.schema
├── LICENSE.md
├── MAINTAINERS.md
├── Procfile
├── README.md
├── SUMMARY.md
├── components
├── App.js
├── AppIcons
│ └── index.js
├── AuthFields
│ ├── index.js
│ ├── store.js
│ ├── styles.js
│ └── validation.js
├── CreatePost
│ ├── createPost.gql
│ ├── index.js
│ ├── store.js
│ └── styles.js
├── Header
│ ├── index.js
│ ├── store.js
│ └── styles.js
├── LinkList
│ ├── index.js
│ └── styles.js
├── NetworkStatus
│ ├── index.js
│ └── styles.js
├── PostInfo
│ ├── getPost.gql
│ ├── index.js
│ ├── store.js
│ └── styles.js
├── PostList
│ ├── allPosts.gql
│ ├── index.js
│ ├── store.js
│ └── styles.js
├── PostUpvoter
│ ├── index.js
│ ├── store.js
│ ├── styles.js
│ └── upvotePost.gql
├── ProjectInfo
│ ├── index.js
│ └── styles.js
├── SignInForm
│ ├── index.js
│ ├── signinUser.gql
│ └── store.js
├── SignUpForm
│ ├── index.js
│ ├── signupUser.gql
│ └── store.js
└── Theme.js
├── containers
└── Default.js
├── docs
├── Architecture
│ ├── app-and-fav-icons.md
│ ├── environment-variables.md
│ ├── readme.md
│ ├── static-files.md
│ ├── static-html-export.md
│ └── type-checking.md
├── Built_with_ran.md
├── Commands.md
├── Contributing.md
├── Deployment
│ ├── README.md
│ ├── aws.md
│ ├── digital-ocean.md
│ ├── heroku.md
│ └── with-now.md
├── Example.md
├── FAQ.md
├── Helper-scripts.md
├── Offline.md
├── Packages.md
├── README.md
├── Roadmap.md
├── Routing.md
├── Styling.md
└── Support.md
├── helper_scripts
├── CL_commands
│ ├── __helpers.js
│ ├── create_component.js
│ ├── create_container.js
│ ├── create_page.js
│ ├── create_route.js
│ └── setup.js
├── http.nginx.conf
├── https.nginx.conf
├── prepare-server-on-digitalocean.sh
├── run-development.sh
├── run-production.sh
└── templates
│ ├── component.hbs
│ ├── component_graphql.hbs
│ ├── component_store.hbs
│ ├── component_style.hbs
│ ├── container.hbs
│ ├── page.hbs
│ ├── readme.hbs
│ ├── reducer.hbs
│ └── route.hbs
├── libraries
├── apolloClient.js
├── commonTypes.js
├── helpers.js
├── middleware.js
├── persist.js
├── redirect.js
├── reducer.js
├── reduxStore.js
├── theme.js
├── validations.js
└── withData.js
├── lighthouse_config.json
├── next.config.js
├── package.json
├── pages
├── _document.js
├── create.js
├── details.js
├── index.js
├── signin.js
└── signup.js
├── routes.js
├── schema.graphql
├── server.js
├── server
└── logger.js
├── static
└── icons
│ ├── android-icon-144x144.png
│ ├── android-icon-192x192.png
│ ├── android-icon-36x36.png
│ ├── android-icon-48x48.png
│ ├── android-icon-72x72.png
│ ├── android-icon-96x96.png
│ ├── apple-icon-114x114.png
│ ├── apple-icon-120x120.png
│ ├── apple-icon-144x144.png
│ ├── apple-icon-152x152.png
│ ├── apple-icon-180x180.png
│ ├── apple-icon-57x57.png
│ ├── apple-icon-60x60.png
│ ├── apple-icon-72x72.png
│ ├── apple-icon-76x76.png
│ ├── apple-icon-precomposed.png
│ ├── apple-icon.png
│ ├── browserconfig.xml
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon-96x96.png
│ ├── favicon.ico
│ ├── manifest.json
│ ├── ms-icon-144x144.png
│ ├── ms-icon-150x150.png
│ ├── ms-icon-310x310.png
│ └── ms-icon-70x70.png
├── types
├── commonTypes.js.flow
└── next.js.flow
└── yarn.lock
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "ran",
3 | "projectOwner": "sly777",
4 | "files": [
5 | "README.md"
6 | ],
7 | "imageSize": 100,
8 | "commit": true,
9 | "contributors": [
10 | {
11 | "login": "Sly777",
12 | "name": "Ilker Güller",
13 | "avatar_url": "https://avatars0.githubusercontent.com/u/694940?v=4",
14 | "profile": "http://ilkerguller.com",
15 | "contributions": [
16 | "code",
17 | "doc",
18 | "review",
19 | "talk"
20 | ]
21 | },
22 | {
23 | "login": "BjornMelgaard",
24 | "name": "bjornmelgaard",
25 | "avatar_url": "https://avatars2.githubusercontent.com/u/7573215?v=4",
26 | "profile": "https://bitbucket.org/melgaardbjorn",
27 | "contributions": [
28 | "code",
29 | "question"
30 | ]
31 | },
32 | {
33 | "login": "astenmies",
34 | "name": "Asten Mies",
35 | "avatar_url": "https://avatars0.githubusercontent.com/u/10152022?v=4",
36 | "profile": "https://github.com/astenmies",
37 | "contributions": [
38 | "code"
39 | ]
40 | },
41 | {
42 | "login": "talensjr",
43 | "name": "Jordi Talens",
44 | "avatar_url": "https://avatars0.githubusercontent.com/u/3647106?v=4",
45 | "profile": "https://github.com/talensjr",
46 | "contributions": [
47 | "code"
48 | ]
49 | }
50 | ]
51 | }
52 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "development": {
4 | "presets": ["next/babel"]
5 | },
6 | "production": {
7 | "presets": ["next/babel"]
8 | },
9 | "test": {
10 | "presets": [
11 | [
12 | "env",
13 | {
14 | "modules": "commonjs"
15 | }
16 | ],
17 | "next/babel"
18 | ]
19 | }
20 | },
21 | "plugins": [
22 | "@babel/plugin-transform-flow-strip-types",
23 | [
24 | "babel-plugin-styled-components",
25 | {
26 | "ssr": true,
27 | "displayName": true,
28 | "preprocess": false
29 | }
30 | ],
31 | "import-graphql"
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | ---
2 | engines:
3 | duplication:
4 | enabled: true
5 | config:
6 | languages:
7 | - javascript
8 | eslint:
9 | enabled: true
10 | config:
11 | config: /.eslintrc
12 | checks:
13 | import/extensions:
14 | enabled: false
15 | prettier/prettier:
16 | enabled: false
17 | fixme:
18 | enabled: true
19 | ratings:
20 | paths:
21 | - "**.js"
22 | - "**.jsx"
23 | exclude_paths:
24 | - node_modules/
25 | - .next/
26 | - .gitattributes
27 |
--------------------------------------------------------------------------------
/.ebextensions/config.config:
--------------------------------------------------------------------------------
1 | # AWS Beanstalk config file
2 | # This script fixes some permissions issues
3 |
4 | packages:
5 | yum:
6 | gcc: []
7 | make: []
8 | openssl-devel: []
9 | libxml2: []
10 | libxml2-devel: []
11 | files:
12 | "/opt/elasticbeanstalk/hooks/appdeploy/post/00_set_tmp_permissions.sh":
13 | mode: "000755"
14 | owner: root
15 | group: root
16 | content: |
17 | #!/usr/bin/env bash
18 | chown -R nodejs:nodejs /tmp/.npm
19 | "/opt/elasticbeanstalk/hooks/appdeploy/pre/50npm.sh" :
20 | mode: "000775"
21 | owner: root
22 | group: root
23 | content: |
24 | #!/bin/bash
25 | function error_exit
26 | {
27 | eventHelper.py --msg "$1" --severity ERROR
28 | exit $2
29 | }
30 |
31 | export HOME=/home/ec2-user # ADDED EXPORT COMMAND
32 | echo "export home" # JUST FOR REMARK
33 |
34 | OUT=$(/opt/elasticbeanstalk/containerfiles/ebnode.py --action npm- install 2>&1) || error_exit "Failed to run npm install. $OUT" $?
35 | echo $OUT
36 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | indent_style = space
7 | indent_size = 2
8 |
--------------------------------------------------------------------------------
/.env-sample:
--------------------------------------------------------------------------------
1 | PORT=3000
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/*{.,-}min.js
2 | __mocks__
3 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "env": {
4 | "browser": true,
5 | "node": true,
6 | "es6": true
7 | },
8 | "extends": ["airbnb", "prettier", "prettier/react", "plugin:import/warnings"],
9 | "plugins": ["prettier", "import", "flowtype"],
10 | "globals": {
11 | "document": true,
12 | "window": true,
13 | "process": true,
14 | "fetch": false,
15 | "ANALYTICS_TRACKING_ID": false,
16 | "AUTH0_CLIENT_ID": false,
17 | "AUTH0_DOMAIN": false,
18 | "GRAPHQL_ENDPOINT": false,
19 | "NEWSLETTER_FORM_ACTION": false,
20 | "NEWSLETTER_FORM_INPUT_NAME": false,
21 | "ON_PRODUCTION": true,
22 | "$Diff": true
23 | },
24 | "rules": {
25 | "react/forbid-prop-types": 0,
26 | "react/jsx-filename-extension": 0,
27 | "react/react-in-jsx-scope": 0,
28 | "class-methods-use-this": 0,
29 | "no-unused-expressions": ["error", { "allowTaggedTemplates": true }],
30 | "no-underscore-dangle": [
31 | "error",
32 | {
33 | "allow": ["__REDUX_DEVTOOLS_EXTENSION_COMPOSE__", "_allPostsMeta"]
34 | }
35 | ],
36 | "react/no-unused-prop-types": 0,
37 | "consistent-return": 0,
38 | "jsx-a11y/anchor-is-valid": 0,
39 | "import/no-extraneous-dependencies": 0,
40 | "prettier/prettier": "error",
41 | "flowtype/define-flow-type": 1,
42 | "react/destructuring-assignment": 0
43 | },
44 | "settings": {
45 | "flowtype": {
46 | "onlyFilesWithFlowAnnotation": true
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 |
3 | [include]
4 |
5 | [libs]
6 | types/
7 |
8 | [lints]
9 |
10 | [options]
11 |
12 | [strict]
13 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes
2 |
3 | ## GITATTRIBUTES FOR WEB PROJECTS
4 | #
5 | # These settings are for any web project.
6 | #
7 | # Details per file setting:
8 | # text These files should be normalized (i.e. convert CRLF to LF).
9 | # binary These files are binary and should be left untouched.
10 | #
11 | # Note that binary is a macro for -text -diff.
12 | ######################################################################
13 |
14 | ## AUTO-DETECT
15 | ## Handle line endings automatically for files detected as
16 | ## text and leave all files detected as binary untouched.
17 | ## This will handle all files NOT defined below.
18 | * text=auto
19 |
20 | ## SOURCE CODE
21 | *.bat text eol=crlf
22 | *.coffee text
23 | *.css text
24 | *.htm text
25 | *.html text
26 | *.inc text
27 | *.ini text
28 | *.js text
29 | *.json text
30 | *.jsx text
31 | *.less text
32 | *.od text
33 | *.onlydata text
34 | *.php text
35 | *.pl text
36 | *.py text
37 | *.rb text
38 | *.sass text
39 | *.scm text
40 | *.scss text
41 | *.sh text eol=lf
42 | *.sql text
43 | *.styl text
44 | *.tag text
45 | *.ts text
46 | *.tsx text
47 | *.xml text
48 | *.xhtml text
49 |
50 | ## DOCKER
51 | *.dockerignore text
52 | Dockerfile text
53 |
54 | ## DOCUMENTATION
55 | *.markdown text
56 | *.md text
57 | *.mdwn text
58 | *.mdown text
59 | *.mkd text
60 | *.mkdn text
61 | *.mdtxt text
62 | *.mdtext text
63 | *.txt text
64 | AUTHORS text
65 | CHANGELOG text
66 | CHANGES text
67 | CONTRIBUTING text
68 | COPYING text
69 | copyright text
70 | *COPYRIGHT* text
71 | INSTALL text
72 | license text
73 | LICENSE text
74 | NEWS text
75 | readme text
76 | *README* text
77 | TODO text
78 |
79 | ## TEMPLATES
80 | *.dot text
81 | *.ejs text
82 | *.haml text
83 | *.handlebars text
84 | *.hbs text
85 | *.hbt text
86 | *.jade text
87 | *.latte text
88 | *.mustache text
89 | *.njk text
90 | *.phtml text
91 | *.tmpl text
92 | *.tpl text
93 | *.twig text
94 |
95 | ## LINTERS
96 | .csslintrc text
97 | .eslintrc text
98 | .jscsrc text
99 | .jshintrc text
100 | .jshintignore text
101 | .stylelintrc text
102 |
103 | ## CONFIGS
104 | *.bowerrc text
105 | *.cnf text
106 | *.conf text
107 | *.config text
108 | .browserslistrc text
109 | .editorconfig text
110 | .gitattributes text
111 | .gitconfig text
112 | .gitignore text
113 | .htaccess text
114 | *.npmignore text
115 | *.yaml text
116 | *.yml text
117 | browserslist text
118 | Makefile text
119 | makefile text
120 |
121 | ## HEROKU
122 | Procfile text
123 | .slugignore text
124 |
125 | ## GRAPHICS
126 | *.ai binary
127 | *.bmp binary
128 | *.eps binary
129 | *.gif binary
130 | *.ico binary
131 | *.jng binary
132 | *.jp2 binary
133 | *.jpg binary
134 | *.jpeg binary
135 | *.jpx binary
136 | *.jxr binary
137 | *.pdf binary
138 | *.png binary
139 | *.psb binary
140 | *.psd binary
141 | *.svg text
142 | *.svgz binary
143 | *.tif binary
144 | *.tiff binary
145 | *.wbmp binary
146 | *.webp binary
147 |
148 | ## AUDIO
149 | *.kar binary
150 | *.m4a binary
151 | *.mid binary
152 | *.midi binary
153 | *.mp3 binary
154 | *.ogg binary
155 | *.ra binary
156 |
157 | ## VIDEO
158 | *.3gpp binary
159 | *.3gp binary
160 | *.as binary
161 | *.asf binary
162 | *.asx binary
163 | *.fla binary
164 | *.flv binary
165 | *.m4v binary
166 | *.mng binary
167 | *.mov binary
168 | *.mp4 binary
169 | *.mpeg binary
170 | *.mpg binary
171 | *.swc binary
172 | *.swf binary
173 | *.webm binary
174 |
175 | ## ARCHIVES
176 | *.7z binary
177 | *.gz binary
178 | *.rar binary
179 | *.tar binary
180 | *.zip binary
181 |
182 | ## FONTS
183 | *.ttf binary
184 | *.eot binary
185 | *.otf binary
186 | *.woff binary
187 | *.woff2 binary
188 |
189 | ## EXECUTABLES
190 | *.exe binary
191 | *.pyc binary
192 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .next/
3 | config.js
4 | .env
5 | public.env
6 | coverage/
7 | out/
8 |
9 | # Created by https://www.gitignore.io/api/node,macos,linux,windows,webstorm,sublimetext,visualstudiocode
10 |
11 | ### Linux ###
12 | *~
13 |
14 | # temporary files which can be created if a process still has a handle open of a deleted file
15 | .fuse_hidden*
16 |
17 | # KDE directory preferences
18 | .directory
19 |
20 | # Linux trash folder which might appear on any partition or disk
21 | .Trash-*
22 |
23 | # .nfs files are created when an open file is removed but is still being accessed
24 | .nfs*
25 |
26 | ### macOS ###
27 | *.DS_Store
28 | .AppleDouble
29 | .LSOverride
30 |
31 | # Icon must end with two \r
32 | Icon
33 |
34 | # Thumbnails
35 | ._*
36 |
37 | # Files that might appear in the root of a volume
38 | .DocumentRevisions-V100
39 | .fseventsd
40 | .Spotlight-V100
41 | .TemporaryItems
42 | .Trashes
43 | .VolumeIcon.icns
44 | .com.apple.timemachine.donotpresent
45 |
46 | # Directories potentially created on remote AFP share
47 | .AppleDB
48 | .AppleDesktop
49 | Network Trash Folder
50 | Temporary Items
51 | .apdisk
52 |
53 | ### Node ###
54 | # Logs
55 | logs
56 | *.log
57 | npm-debug.log*
58 | yarn-debug.log*
59 | yarn-error.log*
60 |
61 | # Runtime data
62 | pids
63 | *.pid
64 | *.seed
65 | *.pid.lock
66 |
67 | # Directory for instrumented libs generated by jscoverage/JSCover
68 | lib-cov
69 |
70 | # Coverage directory used by tools like istanbul
71 | coverage
72 |
73 | # nyc test coverage
74 | .nyc_output
75 |
76 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
77 | .grunt
78 |
79 | # Bower dependency directory (https://bower.io/)
80 | bower_components
81 |
82 | # node-waf configuration
83 | .lock-wscript
84 |
85 | # Compiled binary addons (http://nodejs.org/api/addons.html)
86 | build/Release
87 |
88 | # Dependency directories
89 | node_modules/
90 | jspm_packages/
91 |
92 | # Typescript v1 declaration files
93 | typings/
94 |
95 | # Optional npm cache directory
96 | .npm
97 |
98 | # Optional eslint cache
99 | .eslintcache
100 |
101 | # Optional REPL history
102 | .node_repl_history
103 |
104 | # Output of 'npm pack'
105 | *.tgz
106 |
107 | # Yarn Integrity file
108 | .yarn-integrity
109 |
110 | # dotenv environment variables file
111 | .env
112 |
113 |
114 | ### SublimeText ###
115 | # cache files for sublime text
116 | *.tmlanguage.cache
117 | *.tmPreferences.cache
118 | *.stTheme.cache
119 |
120 | # workspace files are user-specific
121 | *.sublime-workspace
122 |
123 | # project files should be checked into the repository, unless a significant
124 | # proportion of contributors will probably not be using SublimeText
125 | # *.sublime-project
126 |
127 | # sftp configuration file
128 | sftp-config.json
129 |
130 | # Package control specific files
131 | Package Control.last-run
132 | Package Control.ca-list
133 | Package Control.ca-bundle
134 | Package Control.system-ca-bundle
135 | Package Control.cache/
136 | Package Control.ca-certs/
137 | Package Control.merged-ca-bundle
138 | Package Control.user-ca-bundle
139 | oscrypto-ca-bundle.crt
140 | bh_unicode_properties.cache
141 |
142 | # Sublime-github package stores a github token in this file
143 | # https://packagecontrol.io/packages/sublime-github
144 | GitHub.sublime-settings
145 |
146 | ### WebStorm ###
147 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
148 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
149 |
150 | # User-specific stuff:
151 | .idea/**/workspace.xml
152 | .idea/**/tasks.xml
153 | .idea/dictionaries
154 |
155 | # Sensitive or high-churn files:
156 | .idea/**/dataSources/
157 | .idea/**/dataSources.ids
158 | .idea/**/dataSources.xml
159 | .idea/**/dataSources.local.xml
160 | .idea/**/sqlDataSources.xml
161 | .idea/**/dynamic.xml
162 | .idea/**/uiDesigner.xml
163 |
164 | # Gradle:
165 | .idea/**/gradle.xml
166 | .idea/**/libraries
167 |
168 | # CMake
169 | cmake-build-debug/
170 |
171 | # Mongo Explorer plugin:
172 | .idea/**/mongoSettings.xml
173 |
174 | ## File-based project format:
175 | *.iws
176 |
177 | ## Plugin-specific files:
178 |
179 | # IntelliJ
180 | /out/
181 |
182 | # mpeltonen/sbt-idea plugin
183 | .idea_modules/
184 |
185 | # JIRA plugin
186 | atlassian-ide-plugin.xml
187 |
188 | # Cursive Clojure plugin
189 | .idea/replstate.xml
190 |
191 | # Crashlytics plugin (for Android Studio and IntelliJ)
192 | com_crashlytics_export_strings.xml
193 | crashlytics.properties
194 | crashlytics-build.properties
195 | fabric.properties
196 |
197 | ### WebStorm Patch ###
198 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
199 |
200 | # *.iml
201 | # modules.xml
202 | # .idea/misc.xml
203 | # *.ipr
204 |
205 | # Sonarlint plugin
206 | .idea/sonarlint
207 |
208 | ### Windows ###
209 | # Windows thumbnail cache files
210 | Thumbs.db
211 | ehthumbs.db
212 | ehthumbs_vista.db
213 |
214 | # Folder config file
215 | Desktop.ini
216 |
217 | # Recycle Bin used on file shares
218 | $RECYCLE.BIN/
219 |
220 | # Windows Installer files
221 | *.cab
222 | *.msi
223 | *.msm
224 | *.msp
225 |
226 | # Windows shortcuts
227 | *.lnk
228 |
229 | ### VisualStudioCode ###
230 | .vscode/*
231 | !.vscode/settings.json
232 | !.vscode/tasks.json
233 | !.vscode/launch.json
234 | !.vscode/extensions.json
235 |
236 | # End of https://www.gitignore.io/api/node,macos,linux,windows,webstorm,sublimetext,visualstudiocode
237 |
--------------------------------------------------------------------------------
/.graphqlconfig:
--------------------------------------------------------------------------------
1 | {
2 | "schemaPath": "./schema.graphql",
3 | "extensions": {
4 | "endpoints": {
5 | "default": "https://api.graph.cool/simple/v1/cj7ke77fv0e9i0122pflagbvx"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.htaccess:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | #######################################################################
6 | # GENERAL #
7 | #######################################################################
8 |
9 | # Make apache follow sym links to files
10 | Options +FollowSymLinks
11 | # If somebody opens a folder, hide all files from the resulting folder list
12 | IndexIgnore */*
13 |
14 |
15 | #######################################################################
16 | # REWRITING #
17 | #######################################################################
18 |
19 | # Enable rewriting
20 | RewriteEngine On
21 |
22 | # If its not HTTPS
23 | RewriteCond %{HTTPS} off
24 |
25 | # Comment out the RewriteCond above, and uncomment the RewriteCond below if you're using a load balancer (e.g. CloudFlare) for SSL
26 | # RewriteCond %{HTTP:X-Forwarded-Proto} !https
27 |
28 | # Redirect to the same URL with https://, ignoring all further rules if this one is in effect
29 | RewriteRule ^(.*) https://%{HTTP_HOST}/$1 [R,L]
30 |
31 | # If we get to here, it means we are on https://
32 |
33 | # If the file with the specified name in the browser doesn't exist
34 | RewriteCond %{REQUEST_FILENAME} !-f
35 |
36 | # and the directory with the specified name in the browser doesn't exist
37 | RewriteCond %{REQUEST_FILENAME} !-d
38 |
39 | # and we are not opening the root already (otherwise we get a redirect loop)
40 | RewriteCond %{REQUEST_FILENAME} !\/$
41 |
42 | # Rewrite all requests to the root
43 | RewriteRule ^(.*) /
44 |
45 |
46 |
47 |
48 | # Do not cache sw.js, required for offline-first updates.
49 |
50 | Header set Cache-Control "private, no-cache, no-store, proxy-revalidate, no-transform"
51 | Header set Pragma "no-cache"
52 |
53 |
54 |
--------------------------------------------------------------------------------
/.node-version:
--------------------------------------------------------------------------------
1 | 10
2 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea/
3 | .next/
4 | node_modules/
5 | !config.js
6 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 10
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "semi": true
4 | }
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - 8
5 | - 9
6 | - 10
7 |
8 | before_install:
9 | - export PATH=$PATH:`yarn global bin`
10 | - yarn global add greenkeeper-lockfile@1
11 | before_script: greenkeeper-lockfile-update
12 | after_script: greenkeeper-lockfile-upload
13 |
14 | script:
15 | - yarn run build
16 | - yarn run lint
17 |
18 | notifications:
19 | email:
20 | on_failure: change
21 |
22 | cache:
23 | yarn: true
24 | directories:
25 | - node_modules
26 |
--------------------------------------------------------------------------------
/.yarnclean:
--------------------------------------------------------------------------------
1 | # test directories
2 | __tests__
3 | test
4 | tests
5 | powered-test
6 |
7 | # asset directories
8 | docs
9 | doc
10 | website
11 | images
12 | assets
13 |
14 | # examples
15 | example
16 | examples
17 |
18 | # code coverage directories
19 | coverage
20 | .nyc_output
21 |
22 | # build scripts
23 | Makefile
24 | Gulpfile.js
25 | Gruntfile.js
26 |
27 | # configs
28 | .tern-project
29 | .gitattributes
30 | .editorconfig
31 | .*ignore
32 | .eslintrc
33 | .jshintrc
34 | .flowconfig
35 | .documentup.json
36 | .yarn-metadata.json
37 | .*.yml
38 | *.yml
39 |
40 | # misc
41 | *.gz
42 | *.md
43 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ## Code of Conduct
2 |
3 | ### Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ### Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ### Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ### Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ### Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ### Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [http://contributor-covenant.org/version/1/4][version]
72 |
73 | [homepage]: http://contributor-covenant.org
74 | [version]: http://contributor-covenant.org/version/1/4/
75 |
--------------------------------------------------------------------------------
/Graphcool-simplified.schema:
--------------------------------------------------------------------------------
1 | # projectId: cj7ke77fv0e9i0122pflagbvx
2 | # version: 4
3 |
4 | type File @model {
5 | contentType: String!
6 | createdAt: DateTime!
7 | id: ID! @isUnique
8 | name: String!
9 | secret: String! @isUnique
10 | size: Int!
11 | updatedAt: DateTime!
12 | url: String! @isUnique
13 | }
14 |
15 | type User @model {
16 | createdAt: DateTime!
17 | email: String @isUnique
18 | id: ID! @isUnique
19 | password: String
20 | updatedAt: DateTime!
21 | firstName: String!
22 | lastName: String!
23 | }
24 |
25 | type Post @model {
26 | createdAt: DateTime!
27 | id: ID! @isUnique
28 | title: String!
29 | updatedAt: DateTime!
30 | url: String!
31 | votes: Int @defaultValue(value: 0)
32 | }
33 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Ilker Guller
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 |
--------------------------------------------------------------------------------
/MAINTAINERS.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/MAINTAINERS.md
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: npm run start
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | ### RAN : React . GraphQL . Next.js Toolkit
3 |
4 | [](https://opencollective.com/ran/) [](https://opencollective.com/ran/)
5 | [](https://greenkeeper.io/) [](#contributors) [](https://gitter.im/ran-boilerplate/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://travis-ci.org/Sly777/ran) []()
6 | [](https://codeclimate.com/github/Sly777/ran) [](https://snyk.io/test/github/sly777/ran) [](https://www.npmjs.com/package/ran-boilerplate) [](https://github.com/prettier/prettier) [](https://github.com/Sly777/ran)
7 |
8 | ### New version is coming... Follow up here: https://github.com/Sly777/ran/issues/677
9 |
10 | ## Features
11 |
12 | - ***Hot-Reload Ready for Dev***
13 |
14 | - ***Next Generation JavaScript (ES6)***
15 |
16 | - ***Offline Ready (Experimental!)***
17 |
18 | - ***Next Generation CSS (CSS-in-JS)***
19 |
20 | - ***Create New Page in a Second (with CLI)***
21 |
22 | - ***SEO-Ready***
23 |
24 | - ***Performance-first***
25 |
26 | - ***Production Deployment Ready for Now, Digital Ocean, Heroku, and AWS***
27 |
28 | - ***Prettier and ESLint integrated***
29 |
30 | ## How to use
31 |
32 | - Firstly, clone the repo with this command.
33 |
34 | ```bash
35 | git clone --depth=1 https://github.com/Sly777/ran.git RAN
36 | cd RAN
37 | yarn && yarn setup
38 | ```
39 |
40 | - If you are not using Yarn, just run ```npm install && npm run setup``` instead of ```yarn && yarn setup```
41 | - After everything is finished, run ```yarn dev``` (or ```npm run dev```)
42 |
43 | And that's all!
44 |
45 | ## Start coding on browser! (via [Glitch](https://glitch.com))
46 |
47 | Just click the button and then start to work with RAN!
48 |
49 | [](https://glitch.com/edit/#!/import/github/Sly777/ran)
50 |
51 | #### Clean Setup (without example pages & components)
52 |
53 | ```bash
54 | git clone --depth=1 https://github.com/Sly777/ran.git RAN
55 | cd RAN
56 | yarn && yarn setup:clean
57 | ```
58 |
59 | #### Beta Version (Unstable)
60 |
61 | Also, there is a ```beta``` version for new features & fixes that we are testing before release. To access beta;
62 |
63 | ```bash
64 | git clone --depth=1 -b beta https://github.com/Sly777/ran.git RAN_beta
65 | cd RAN_beta
66 | yarn && yarn setup
67 | ```
68 |
69 | It can be unstable, so that's why please use stable version if you are working on the project that is in production.
70 |
71 | ## Example
72 |
73 | [Click here](https://ran.now.sh/) to see example project to understand how RAN! works on production. I used [graph.cool](https://graph.cool/) service for GraphQL and [now](https://zeit.co/now) for hosting in the example.
74 |
75 | ## Commands
76 |
77 | Best feature of RAN! is **CL commands**. You can just run one command to create page with route! [Click here](docs/Commands.md) to see details how It works on RAN!.
78 |
79 | 
80 | 
81 |
82 | ## Documentation
83 |
84 | [Click here](https://www.rantoolkit.com/) to see all details of RAN!
85 |
86 | ## FAQ
87 |
88 | [Click here](docs/FAQ.md) for FAQ of RAN! If it doesn't solve your problem, feel free to open an issue on GitHub!
89 |
90 | ## License
91 |
92 | This project is licensed under the MIT license, Copyright (c) 2017 Ilker Guller. For more information see [LICENSE.md](LICENSE.md).
93 |
94 | ## Contributing
95 |
96 | Please read [Contributing doc](docs/Contributing.md) for details on our code of conduct, and the process for submitting pull requests.
97 |
98 | ## Versioning
99 |
100 | RAN! is using [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/Sly777/ran/tags).
101 |
102 | ## Author
103 |
104 | * **Ilker Guller** - [website](http://ilkerguller.com) / [twitter](https://twitter.com/the_bluescreen)
105 |
106 | ## Contributors
107 |
108 | Thanks goes to these wonderful people:
109 |
110 |
111 |
112 | ### Backers
113 |
114 | Thank you to all our backers! [[Become a backer](https://opencollective.com/ran#backer)]
115 |
116 |
117 |
118 | ### Sponsors
119 |
120 | Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/ran#sponsor))
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | # App Showcase
130 |
131 | [WIP]
132 |
--------------------------------------------------------------------------------
/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Summary
2 |
3 | * [Read Me](README.md)
4 | * [Example](docs/Example.md)
5 | * [Support](docs/Support.md)
6 | * [Documentation](docs/README.md)
7 | * [Deployment](docs/Deployment/README.md)
8 | * [Now](docs/Deployment/with-now.md)
9 | * [Amazon \(AWS\)](docs/Deployment/aws.md)
10 | * [Heroku](docs/Deployment/heroku.md)
11 | * [Digital Ocean](docs/Deployment/digital-ocean.md)
12 | * [Architecture](docs/Architecture/readme.md)
13 | * [App and Fav Icons](docs/Architecture/app-and-fav-icons.md)
14 | * [Static HTML Export](docs/Architecture/static-html-export.md)
15 | * [Static Files](docs/Architecture/static-files.md)
16 | * [Environment Variables](docs/Architecture/environment-variables.md)
17 | * [Type Checking](docs/Architecture/type-checking.md)
18 | * [Commands](docs/Commands.md)
19 | * [Styling](docs/Styling.md)
20 | * [Routing](docs/Routing.md)
21 | * [Offline Support](docs/Offline.md)
22 | * [Helper Scripts](docs/Helper-scripts.md)
23 | * [Roadmap](docs/Roadmap.md)
24 | * [Contributing](docs/Contributing.md)
25 | * [Built with RAN!](docs/Built_with_ran.md)
26 | * [FAQ](docs/FAQ.md)
27 |
28 |
--------------------------------------------------------------------------------
/components/App.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import PropTypes from 'prop-types';
4 | import { ThemeProvider, createGlobalStyle } from 'styled-components';
5 | import color from 'color';
6 | import themeList from '../libraries/theme';
7 | import { App as ThemedApp } from './Theme';
8 |
9 | let offlineInstalled = false;
10 |
11 | type Props = {
12 | children: React.Node,
13 | theme?: string
14 | };
15 |
16 | const GlobalStyle = createGlobalStyle`
17 | * {
18 | font-family: Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace, serif;
19 | }
20 | body {
21 | margin: 0;
22 | padding: 0;
23 | }
24 | `;
25 |
26 | const App = ({ children, theme }: Props) => {
27 | const themeName = !themeList[theme] ? 'main' : theme;
28 | if (!themeList[themeName].helper) themeList[themeName].helper = color;
29 |
30 | if (process.env.OFFLINE_SUPPORT && process.browser && !offlineInstalled) {
31 | const OfflinePlugin = require('offline-plugin/runtime'); // eslint-disable-line global-require
32 |
33 | OfflinePlugin.install({
34 | onUpdateReady() {
35 | OfflinePlugin.applyUpdate();
36 | },
37 | onUpdated() {
38 | window.location.reload();
39 | }
40 | });
41 | offlineInstalled = true;
42 | }
43 |
44 | return (
45 |
46 |
47 |
48 | {children}
49 |
50 |
51 | );
52 | };
53 |
54 | App.defaultProps = {
55 | theme: 'main'
56 | };
57 |
58 | App.propTypes = {
59 | children: PropTypes.array.isRequired,
60 | theme: PropTypes.string
61 | };
62 |
63 | export default App;
64 |
--------------------------------------------------------------------------------
/components/AppIcons/index.js:
--------------------------------------------------------------------------------
1 | export default () => [
2 | ,
8 | ,
14 | ,
20 | ,
26 | ,
32 | ,
38 | ,
44 | ,
50 | ,
56 | ,
63 | ,
70 | ,
77 | ,
84 | ,
85 | ,
90 |
91 | ];
92 |
--------------------------------------------------------------------------------
/components/AuthFields/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import PropTypes from 'prop-types';
4 | import { Main, SubmitButton } from './styles';
5 |
6 | type Props = {
7 | selectFields: string,
8 | fields: Array<{ key: number, attr: { [string]: string } }>,
9 | handleTouch: Function,
10 | handleChange: Function,
11 | handleSubmit: Function,
12 | touched: boolean,
13 | errors: ?{ [string]: string }
14 | };
15 |
16 | const AuthFields = (props: Props) => {
17 | const {
18 | selectFields,
19 | fields,
20 | handleTouch,
21 | handleChange,
22 | handleSubmit,
23 | touched,
24 | errors
25 | } = props;
26 | const mapFields = fields.map(field => (
27 |
28 |
35 | {errors &&
{errors[field.attr.name]}
}
36 |
37 | ));
38 | const authMethod =
39 | (selectFields === 'signinFields' && 'Sign In') || 'Sign Up';
40 | return (
41 |
42 | {authMethod}
43 |
54 |
55 | );
56 | };
57 |
58 | AuthFields.propTypes = {
59 | selectFields: PropTypes.string.isRequired,
60 | fields: PropTypes.array.isRequired,
61 | handleTouch: PropTypes.func.isRequired,
62 | handleChange: PropTypes.func.isRequired,
63 | handleSubmit: PropTypes.func.isRequired,
64 | touched: PropTypes.bool.isRequired,
65 | errors: PropTypes.object.isRequired
66 | };
67 |
68 | export default AuthFields;
69 |
--------------------------------------------------------------------------------
/components/AuthFields/store.js:
--------------------------------------------------------------------------------
1 | import persist from '../../libraries/persist';
2 |
3 | // Constants
4 | export const AUTH_SIGNIN = 'AUTH/SIGNIN';
5 | export const AUTH_SIGNOUT = 'AUTH/SIGNOUT';
6 | export const AUTH_SERVERERROR = 'AUTH/SERVERERROR';
7 |
8 | // Initial State
9 | const initialState = {
10 | authenticated: false,
11 | token: null,
12 | error: null
13 | };
14 |
15 | // Reducer
16 | const reducer = (state = initialState, action) => {
17 | switch (action.type) {
18 | case AUTH_SIGNIN:
19 | return {
20 | ...state,
21 | authenticated: true,
22 | token: action.token,
23 | error: null
24 | };
25 | case AUTH_SIGNOUT:
26 | return { ...state, authenticated: false, token: null, error: null };
27 | case AUTH_SERVERERROR:
28 | return { ...state, authenticated: false, error: action.error };
29 | default:
30 | return state;
31 | }
32 | };
33 |
34 | // Action creators
35 | const actionCreators = {};
36 |
37 | actionCreators.signIn = token => ({ type: AUTH_SIGNIN, token });
38 | actionCreators.signOut = () => ({ type: AUTH_SIGNOUT });
39 |
40 | // Discpatchers
41 | const dispatchers = {};
42 |
43 | dispatchers.signIn = token => {
44 | persist.willSetAccessToken(token);
45 | return actionCreators.signIn(token);
46 | };
47 |
48 | dispatchers.signOut = () => {
49 | persist.willRemoveAccessToken();
50 | return actionCreators.signOut();
51 | };
52 |
53 | export { actionCreators, reducer, dispatchers };
54 |
--------------------------------------------------------------------------------
/components/AuthFields/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import * as T from '../Theme';
3 |
4 | export const Main = styled.div`
5 | border-bottom: 1px solid #ececec;
6 | padding-bottom: 20px;
7 | margin-bottom: 20px;
8 |
9 | > h1 {
10 | font-size: 20px;
11 | }
12 |
13 | > input {
14 | display: block;
15 | margin-bottom: 10px;
16 | }
17 | `;
18 |
19 | export const SubmitButton = styled(T.Button)`
20 | opacity: ${({ touched }) => (touched ? 1 : 0.5)};
21 | `;
22 |
--------------------------------------------------------------------------------
/components/AuthFields/validation.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { isStringEmpty, isEmail } from '../../libraries/validations';
3 |
4 | export default (values: { [string]: ?string }) => {
5 | const errorsBuffer = {};
6 |
7 | Object.entries(values).forEach(([key, value]) => {
8 | if (typeof value === 'string') {
9 | if (isStringEmpty(value)) {
10 | errorsBuffer[key] = 'Required';
11 | return;
12 | }
13 |
14 | if (key === 'email' && !isEmail(value)) {
15 | errorsBuffer[key] = 'Invalid email address';
16 | return;
17 | }
18 |
19 | if (
20 | key === 'password' &&
21 | values.password &&
22 | values.password.length <= 3
23 | ) {
24 | errorsBuffer[key] = 'Must be at least 4 characters';
25 | }
26 | } else {
27 | errorsBuffer[key] = 'Required';
28 | }
29 | });
30 |
31 | // can be false or object
32 | const errors = Object.keys(errorsBuffer).length > 0 ? errorsBuffer : false;
33 |
34 | return {
35 | errors,
36 | touched: true
37 | };
38 | };
39 |
--------------------------------------------------------------------------------
/components/CreatePost/createPost.gql:
--------------------------------------------------------------------------------
1 | mutation createPost($title: String!, $url: String!) {
2 | createPost(title: $title, url: $url) {
3 | id
4 | title
5 | votes
6 | url
7 | createdAt
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/components/CreatePost/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import PropTypes from 'prop-types';
4 | import { Form, SubmitButton } from './styles';
5 | import { Router } from '../../routes';
6 | import connect from './store';
7 |
8 | type Props = {
9 | mutations: {
10 | createPost: Function
11 | }
12 | };
13 |
14 | class CreateForm extends React.Component {
15 | static propTypes = {
16 | mutations: PropTypes.shape({
17 | createPost: PropTypes.func.isRequired
18 | }).isRequired
19 | };
20 |
21 | handleSubmit = e => {
22 | e.preventDefault();
23 |
24 | const title = e.target.elements.title.value;
25 | let url = e.target.elements.url.value;
26 |
27 | if (title === '' || url === '') {
28 | // eslint-disable-next-line no-alert
29 | window.alert('Both fields are required.');
30 | return false;
31 | }
32 |
33 | // prepend http if missing from url
34 | if (!url.match(/^[a-zA-Z]+:\/\//)) {
35 | url = `http://${url}`;
36 | }
37 |
38 | this.props.mutations.createPost(title, url);
39 |
40 | // reset form
41 | e.target.elements.title.value = '';
42 | e.target.elements.url.value = '';
43 |
44 | Router.pushRoute('/');
45 | };
46 |
47 | render = () => (
48 |
54 | );
55 | }
56 |
57 | export default connect(CreateForm);
58 |
--------------------------------------------------------------------------------
/components/CreatePost/store.js:
--------------------------------------------------------------------------------
1 | import { graphql } from 'react-apollo';
2 | import createPostGql from './createPost.gql';
3 |
4 | export const withMutation = graphql(createPostGql, {
5 | props: ({ mutate }) => ({
6 | mutations: {
7 | createPost: (title, url) =>
8 | mutate({
9 | variables: { title, url },
10 | updateQueries: {
11 | allPosts: (previousResult, { mutationResult }) => {
12 | const newPost = mutationResult.data.createPost;
13 | return Object.assign({}, previousResult, {
14 | // Append the new post
15 | allPosts: [newPost, ...previousResult.allPosts]
16 | });
17 | }
18 | }
19 | })
20 | }
21 | })
22 | });
23 |
24 | export default comp => withMutation(comp);
25 |
--------------------------------------------------------------------------------
/components/CreatePost/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Button } from '../Theme';
3 |
4 | // eslint-disable-next-line import/prefer-default-export
5 | export const Form = styled.form`
6 | border-bottom: 1px solid #ececec;
7 | padding-bottom: 20px;
8 | margin-bottom: 20px;
9 |
10 | > h1 {
11 | font-size: 20px;
12 | }
13 | > input {
14 | display: block;
15 | margin-bottom: 10px;
16 | }
17 | `;
18 | export { Button as SubmitButton };
19 |
--------------------------------------------------------------------------------
/components/Header/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import PropTypes from 'prop-types';
4 | import LinkList from '../LinkList';
5 | import { Header as StyledHeader, Img } from './styles';
6 | import connect from './store';
7 |
8 | type Props = {
9 | pathname: string,
10 | authenticated?: boolean,
11 | actions: {
12 | logout: Function
13 | }
14 | };
15 |
16 | const Header = ({ pathname, authenticated, actions: { logout } }: Props) => (
17 |
18 |
22 |
27 |
28 | );
29 |
30 | Header.defaultProps = {
31 | authenticated: false
32 | };
33 |
34 | Header.propTypes = {
35 | pathname: PropTypes.string.isRequired,
36 | authenticated: PropTypes.bool,
37 | actions: PropTypes.shape({
38 | logout: PropTypes.func.isRequired
39 | }).isRequired
40 | };
41 |
42 | export default connect(Header);
43 |
--------------------------------------------------------------------------------
/components/Header/store.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { dispatchers } from '../AuthFields/store';
3 |
4 | const mapStateToProps = state => ({
5 | authenticated: state.auth.authenticated
6 | });
7 |
8 | const mapDispatchToProps = dispatch => ({
9 | actions: {
10 | logout: () => dispatch(dispatchers.signOut())
11 | }
12 | });
13 |
14 | export default comp =>
15 | connect(
16 | mapStateToProps,
17 | mapDispatchToProps
18 | )(comp);
19 |
--------------------------------------------------------------------------------
/components/Header/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | // eslint-disable-next-line import/prefer-default-export
4 | export const Header = styled.header`
5 | margin-bottom: 5px;
6 | padding: 10px;
7 | border-bottom: 2px solid #e94fc3;
8 | `;
9 |
10 | export const Img = styled.img`
11 | width: 100px;
12 | display: inline-block;
13 | vertical-align: middle;
14 | margin-right: 15px;
15 | `;
16 |
--------------------------------------------------------------------------------
/components/LinkList/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import PropTypes from 'prop-types';
4 | import { Link } from '../../routes';
5 | import { A, LogOutButton, Nav, Externals } from './styles';
6 |
7 | type Props = {
8 | pathname: string,
9 | authenticated: boolean,
10 | logout: Function
11 | };
12 |
13 | const LinkList = ({ pathname, authenticated, logout }: Props) => (
14 |
15 |
16 | Main Page
17 |
18 |
19 | Create
20 |
21 | {!authenticated && (
22 |
23 | SignIn
24 |
25 | )}
26 | {!authenticated && (
27 |
28 | SignUp
29 |
30 | )}
31 | {authenticated && (
32 | logout()}
36 | active={pathname === '/sign_up'}
37 | >
38 | LogOut
39 |
40 | )}
41 |
42 |
43 | RAN! Documentation
44 |
45 |
46 | RAN! @ Github
47 |
48 |
49 |
50 | );
51 |
52 | LinkList.propTypes = {
53 | pathname: PropTypes.string.isRequired,
54 | authenticated: PropTypes.bool.isRequired,
55 | logout: PropTypes.func.isRequired
56 | };
57 |
58 | export default LinkList;
59 |
--------------------------------------------------------------------------------
/components/LinkList/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import * as T from '../Theme';
3 |
4 | export const A = styled(T.A)`
5 | font-size: 14px;
6 | margin-right: 15px;
7 | cursor: pointer;
8 | text-decoration: ${({ active }) => (active ? 'underline' : 'none')};
9 | `;
10 |
11 | export const LogOutButton = styled(T.Button)`
12 | display: inline-block;
13 | margin-right: 15px;
14 | cursor: pointer;
15 | text-decoration: ${({ active }) => (active ? 'underline' : 'none')};
16 | `;
17 |
18 | export const Nav = styled.nav`
19 | display: inline-block;
20 | `;
21 |
22 | export const Externals = styled.div`
23 | display: inline-block;
24 | border-left: 2px solid lightgray;
25 | padding-left: 15px;
26 | `;
27 |
--------------------------------------------------------------------------------
/components/NetworkStatus/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import * as styles from './styles';
4 |
5 | type Props = {};
6 | type State = {
7 | online: boolean
8 | };
9 |
10 | class NetworkStatus extends React.Component {
11 | state = {
12 | online: true
13 | };
14 |
15 | componentDidMount() {
16 | if (typeof window !== 'undefined') {
17 | window.addEventListener('online', this.updateOnlineStatus);
18 | window.addEventListener('offline', this.updateOnlineStatus);
19 | }
20 | }
21 |
22 | updateOnlineStatus = () => {
23 | const condition = navigator.onLine ? 'online' : 'offline';
24 | if (condition === 'offline') {
25 | this.setState({ online: false });
26 | } else if (condition === 'online') {
27 | this.setState({ online: true });
28 | }
29 | };
30 |
31 | render() {
32 | return (
33 |
34 | {!this.state.online && (
35 |
36 | Offline
37 |
38 | )}
39 |
40 | );
41 | }
42 | }
43 |
44 | export default NetworkStatus;
45 |
--------------------------------------------------------------------------------
/components/NetworkStatus/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const Root = styled.div`
4 | position: fixed;
5 | left: 30px;
6 | bottom: 30px;
7 | `;
8 |
9 | const Label = styled.div`
10 | background: red;
11 | color: #fff;
12 | font-weight: bold;
13 | box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
14 | padding: 10px;
15 | `;
16 |
17 | export { Root, Label };
18 |
--------------------------------------------------------------------------------
/components/PostInfo/getPost.gql:
--------------------------------------------------------------------------------
1 | query getPost($postId: ID!) {
2 | Post(id: $postId) {
3 | title
4 | votes
5 | id
6 | url
7 | createdAt
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/components/PostInfo/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import moment from 'moment';
4 | import PropTypes from 'prop-types';
5 | import { Section, A } from './styles';
6 | import connect from './store';
7 |
8 | type Props = {
9 | loading: boolean,
10 | Post?: Post,
11 | error?: string,
12 | postId: string,
13 | postName: string
14 | };
15 |
16 | const PostInfo = ({ loading, Post, error }: Props) => {
17 | if (loading) {
18 | return (
19 |
22 | );
23 | }
24 |
25 | if (error) {
26 | console.log(error); // eslint-disable-line no-console
27 | window.alert('Load error, check console'); // eslint-disable-line no-alert
28 | return;
29 | }
30 |
31 | return (
32 |
33 | {Post && Post.title}
34 |
35 |
36 | ID: {Post && Post.id}
37 |
38 | |
39 |
40 | Created At:{' '}
41 | {moment(Post && Post.createdAt).format('DD.MM.YYYY kk:mm')}
42 |
43 |
44 |
45 |
50 | {Post && Post.url}
51 |
52 |
53 |
54 | );
55 | };
56 |
57 | PostInfo.propTypes = {
58 | loading: PropTypes.bool.isRequired,
59 | Post: PropTypes.object,
60 | error: PropTypes.object,
61 | postId: PropTypes.string.isRequired,
62 | postTitle: PropTypes.string.isRequired
63 | };
64 |
65 | PostInfo.defaultProps = {
66 | Post: null,
67 | error: null
68 | };
69 |
70 | export default connect(PostInfo);
71 |
--------------------------------------------------------------------------------
/components/PostInfo/store.js:
--------------------------------------------------------------------------------
1 | import { graphql } from 'react-apollo';
2 | import getPostGql from './getPost.gql';
3 |
4 | const withData = graphql(getPostGql, {
5 | options: ({ postId }) => ({
6 | variables: {
7 | postId
8 | }
9 | }),
10 | props: ({ data: { loading, Post, error } }) => ({
11 | loading,
12 | Post,
13 | error
14 | })
15 | });
16 |
17 | export default comp => withData(comp);
18 |
--------------------------------------------------------------------------------
/components/PostInfo/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { A } from '../Theme';
3 |
4 | // eslint-disable-next-line import/prefer-default-export
5 | export const Section = styled.section`
6 | padding: 10px 15px;
7 | padding-bottom: 20px;
8 |
9 | > h1 {
10 | margin-top: 0;
11 | }
12 |
13 | > p {
14 | font-size: 17px;
15 | }
16 | `;
17 |
18 | export { A };
19 |
--------------------------------------------------------------------------------
/components/PostList/allPosts.gql:
--------------------------------------------------------------------------------
1 | query allPosts($first: Int!, $skip: Int!) {
2 | allPosts(orderBy: createdAt_DESC, first: $first, skip: $skip) {
3 | id
4 | title
5 | votes
6 | createdAt
7 | }
8 | _allPostsMeta {
9 | count
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/components/PostList/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import PropTypes from 'prop-types';
4 | import { Link } from '../../routes';
5 | import PostUpvoter from '../PostUpvoter';
6 | import {
7 | Main,
8 | ItemList,
9 | Item,
10 | Index,
11 | Title,
12 | ShowMore,
13 | Loading
14 | } from './styles';
15 | import connect from './store';
16 |
17 | type Props = {
18 | data: {
19 | allPosts: Array,
20 | _allPostsMeta: { count: number },
21 | loading: boolean
22 | },
23 | loadMorePosts: () => void
24 | };
25 |
26 | const PostList = ({ data, loadMorePosts }: Props) => {
27 | if (data.allPosts && data.allPosts.length) {
28 | const areMorePosts = data.allPosts.length < data._allPostsMeta.count;
29 | return (
30 |
31 |
32 | {data.allPosts.map((post, index) => (
33 | -
34 |
35 |
{index + 1}.
36 |
44 |
{post.title}
45 |
46 |
47 |
48 |
49 | ))}
50 |
51 | {areMorePosts ? (
52 | loadMorePosts()}>
53 | {data.loading ? 'Loading...' : 'Show More'}
54 |
55 | ) : (
56 | ''
57 | )}
58 |
59 | );
60 | }
61 | return Loading ;
62 | };
63 |
64 | PostList.propTypes = {
65 | data: PropTypes.object.isRequired,
66 | loadMorePosts: PropTypes.func.isRequired
67 | };
68 |
69 | export default connect(PostList);
70 |
--------------------------------------------------------------------------------
/components/PostList/store.js:
--------------------------------------------------------------------------------
1 | import { graphql } from 'react-apollo';
2 | import allPostsGql from './allPosts.gql';
3 |
4 | const POSTS_PER_PAGE = 10;
5 |
6 | const withData = graphql(allPostsGql, {
7 | options: () => ({
8 | variables: {
9 | skip: 0,
10 | first: POSTS_PER_PAGE
11 | }
12 | }),
13 | props: ({ data }) => ({
14 | data,
15 | loadMorePosts: () =>
16 | data.fetchMore({
17 | variables: {
18 | skip: data.allPosts.length
19 | },
20 | updateQuery: (previousResult, { fetchMoreResult }) => {
21 | if (!fetchMoreResult) {
22 | return previousResult;
23 | }
24 | return Object.assign({}, previousResult, {
25 | // Append the new posts results to the old one
26 | allPosts: [...previousResult.allPosts, ...fetchMoreResult.allPosts]
27 | });
28 | }
29 | })
30 | })
31 | });
32 |
33 | export default comp => withData(comp);
34 |
--------------------------------------------------------------------------------
/components/PostList/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import * as T from '../Theme';
3 |
4 | export const Main = styled.section`
5 | padding: 10px 15px;
6 | padding-bottom: 20px;
7 | `;
8 |
9 | export const Item = styled.li`
10 | display: block;
11 | margin-bottom: 10px;
12 | `;
13 |
14 | export const Loading = styled.div`
15 | align-items: center;
16 | display: flex;
17 | `;
18 |
19 | export const Title = styled(T.A)`
20 | font-size: 14px;
21 | margin-right: 10px;
22 | text-decoration: none;
23 | padding-bottom: 0;
24 | border: 0;
25 | `;
26 |
27 | export const Index = styled.span`
28 | font-size: 14px;
29 | margin-right: 5px;
30 | `;
31 |
32 | export const ItemList = styled.ul`
33 | margin: 0;
34 | padding: 0;
35 | `;
36 |
37 | export const ShowMore = styled(T.Button)`
38 | &:before {
39 | align-self: center;
40 | border-style: solid;
41 | border-width: 6px 4px 0 4px;
42 | border-color: #ffffff transparent transparent transparent;
43 | content: '';
44 | height: 0;
45 | margin-right: 5px;
46 | width: 0;
47 | }
48 | `;
49 |
--------------------------------------------------------------------------------
/components/PostUpvoter/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import PropTypes from 'prop-types';
4 | import connect from './store';
5 | import { UpvoteButton } from './styles';
6 |
7 | type Props = {
8 | upvote: (string | number, number) => void,
9 | id: string,
10 | votes?: number
11 | };
12 |
13 | const PostUpvoter = ({ upvote, votes, id }: Props) => (
14 | upvote(id, votes + 1)}>{votes}
15 | );
16 |
17 | PostUpvoter.propTypes = {
18 | upvote: PropTypes.func.isRequired,
19 | votes: PropTypes.number,
20 | id: PropTypes.string.isRequired
21 | };
22 |
23 | PostUpvoter.defaultProps = {
24 | votes: 0
25 | };
26 |
27 | export default connect(PostUpvoter);
28 |
--------------------------------------------------------------------------------
/components/PostUpvoter/store.js:
--------------------------------------------------------------------------------
1 | import { graphql } from 'react-apollo';
2 | import upvotePostGql from './upvotePost.gql';
3 |
4 | const withMutation = graphql(upvotePostGql, {
5 | props: ({ ownProps, mutate }) => ({
6 | upvote: (id, votes) =>
7 | mutate({
8 | variables: { id, votes },
9 | optimisticResponse: {
10 | __typename: 'Mutation',
11 | updatePost: {
12 | __typename: 'Post',
13 | id: ownProps.id,
14 | votes: ownProps.votes + 1
15 | }
16 | }
17 | })
18 | })
19 | });
20 |
21 | export default comp => withMutation(comp);
22 |
--------------------------------------------------------------------------------
/components/PostUpvoter/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import * as T from '../Theme';
3 |
4 | // eslint-disable-next-line import/prefer-default-export
5 | export const UpvoteButton = styled(T.Button)`
6 | display: inline-block;
7 | background-color: transparent;
8 | border: 1px solid #e4e4e4;
9 | color: #000;
10 |
11 | &:active {
12 | background-color: transparent;
13 | }
14 |
15 | &:before {
16 | content: '▲';
17 | margin-right: 7px;
18 | }
19 | `;
20 |
--------------------------------------------------------------------------------
/components/PostUpvoter/upvotePost.gql:
--------------------------------------------------------------------------------
1 | mutation updatePost($id: ID!, $votes: Int) {
2 | updatePost(id: $id, votes: $votes) {
3 | id
4 | __typename
5 | votes
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/components/ProjectInfo/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import { ProjectInfo as StyledProjectInfo } from './styles';
4 |
5 | const ProjectInfo = () => (
6 |
7 |
8 | Example app of RAN! In the interest of fostering an open and welcoming
9 | environment, we as contributors, maintainers and writers pledge to making
10 | participation in our project and our community a harassment-free
11 | experience for everyone, regardless of age, body size, disability,
12 | ethnicity, gender identity and expression, level of experience,
13 | nationality, personal appearance, race, religion, or sexual identity and
14 | orientation. Please be kind and careful when you write something here. It
15 | is just showing how RAN! works on production. These posts are not related
16 | to RAN, Author of RAN, Contributors and Maintainers. Thank you for being
17 | kind, funny and awesome. For details, Please{' '}
18 |
23 | click here.
24 |
25 |
26 |
27 | );
28 |
29 | export default ProjectInfo;
30 |
--------------------------------------------------------------------------------
/components/ProjectInfo/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | // eslint-disable-next-line import/prefer-default-export
4 | export const ProjectInfo = styled.div`
5 | padding: 5px;
6 |
7 | > p {
8 | font-size: 11px;
9 | margin: 0;
10 | }
11 | `;
12 |
--------------------------------------------------------------------------------
/components/SignInForm/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import PropTypes from 'prop-types';
4 | import AuthFields from '../AuthFields';
5 | import validate from '../AuthFields/validation';
6 | import connect from './store';
7 |
8 | type Props = {
9 | mutations: {
10 | signIn: Object => Promise
11 | },
12 | actions: {
13 | signIn: string => void
14 | }
15 | };
16 |
17 | type State = {
18 | errors: Object,
19 | serverErrors: {
20 | message?: string
21 | },
22 | touched: boolean,
23 | email?: string,
24 | password?: string
25 | };
26 |
27 | class SignInForm extends React.Component {
28 | formFields = [
29 | { key: 1, attr: { name: 'email', type: 'email', label: 'Email' } },
30 | { key: 2, attr: { name: 'password', type: 'password', label: 'Password' } }
31 | ];
32 |
33 | static propTypes = {
34 | mutations: PropTypes.shape({
35 | signIn: PropTypes.func.isRequired
36 | }).isRequired,
37 | actions: PropTypes.shape({
38 | signIn: PropTypes.func.isRequired
39 | }).isRequired
40 | };
41 |
42 | state = {
43 | errors: {},
44 | serverErrors: {},
45 | touched: false
46 | };
47 |
48 | getServerErrors(err: { graphQLErrors?: Array<{ message: string }> }) {
49 | if (err.graphQLErrors) {
50 | const obj = {};
51 | obj.message = err.graphQLErrors[0].message;
52 | this.setState({
53 | serverErrors: obj
54 | });
55 | }
56 | }
57 |
58 | handleTouch = () => {
59 | this.setState({ touched: true });
60 | };
61 |
62 | handleChange = (e: SyntheticEvent) => {
63 | const fieldValue = e.currentTarget.value;
64 | const fieldName = e.currentTarget.name;
65 | const obj = {};
66 | obj[fieldName] = fieldValue;
67 | this.setState(obj);
68 | };
69 |
70 | handleSubmit(e: SyntheticEvent, valuesPack) {
71 | e.preventDefault();
72 |
73 | // reset state
74 | this.setState({
75 | errors: {},
76 | serverErrors: {}
77 | });
78 |
79 | const handleValidate = validate(valuesPack);
80 |
81 | if (handleValidate.touched) {
82 | this.setState({ touched: handleValidate.touched });
83 | }
84 | if (handleValidate.errors) {
85 | return this.setState({ errors: handleValidate.errors });
86 | }
87 |
88 | this.props.mutations
89 | .signIn(valuesPack)
90 | .then((response: { data: { signinUser: { token: string } } }) => {
91 | if (response.data) {
92 | this.props.actions.signIn(response.data.signinUser.token);
93 | }
94 | })
95 | .catch((err: { graphQLErrors?: Array<{ message: string }> }) => {
96 | this.getServerErrors(err);
97 | });
98 | }
99 |
100 | render() {
101 | const fields = this.formFields;
102 | // Packing all the necessary auth field states
103 | const valuesPack = {};
104 |
105 | fields.map(x => {
106 | const y: string = x.attr.name;
107 | if (this.state[y]) {
108 | valuesPack[y] = this.state[y];
109 | }
110 | return valuesPack;
111 | });
112 |
113 | return (
114 |
115 |
) => {
117 | this.handleSubmit(e, valuesPack);
118 | }}
119 | handleChange={this.handleChange}
120 | fields={fields}
121 | errors={this.state.errors}
122 | touched={this.state.touched}
123 | handleTouch={this.handleTouch}
124 | selectFields="signinFields"
125 | />
126 |
127 |
128 | {Object.keys(this.state.errors).length === 0 &&
129 | this.state.serverErrors.message}
130 |
131 |
132 | );
133 | }
134 | }
135 |
136 | export default connect(SignInForm);
137 |
--------------------------------------------------------------------------------
/components/SignInForm/signinUser.gql:
--------------------------------------------------------------------------------
1 | mutation signinUser($email: String!, $password: String!) {
2 | signinUser(email: { email: $email, password: $password }) {
3 | token
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/components/SignInForm/store.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { graphql } from 'react-apollo';
3 | import { dispatchers } from '../AuthFields/store';
4 | import signInGql from './signinUser.gql';
5 |
6 | const withMutation = graphql(signInGql, {
7 | props: ({ mutate }) => ({
8 | mutations: {
9 | signIn: ({ email, password }) =>
10 | mutate({
11 | variables: { email, password }
12 | })
13 | }
14 | })
15 | });
16 |
17 | const mapDispatchToProps = dispatch => ({
18 | actions: {
19 | signIn(token) {
20 | dispatch(dispatchers.signIn(token));
21 | }
22 | }
23 | });
24 |
25 | export default comp => {
26 | const compWithApollo = withMutation(comp);
27 | return connect(
28 | null,
29 | mapDispatchToProps
30 | )(compWithApollo);
31 | };
32 |
--------------------------------------------------------------------------------
/components/SignUpForm/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import PropTypes from 'prop-types';
4 | import AuthFields from '../AuthFields';
5 | import validate from '../AuthFields/validation';
6 | import connect from './store';
7 |
8 | type Props = {
9 | mutations: {
10 | signUp: Object => Promise
11 | },
12 | actions: {
13 | signIn: string => void
14 | }
15 | };
16 |
17 | type State = {
18 | errors: Object,
19 | serverErrors: {
20 | message?: string
21 | },
22 | touched: boolean,
23 | email?: string,
24 | password?: string
25 | };
26 |
27 | class SignUpForm extends React.Component {
28 | formFields = [
29 | { key: 1, attr: { name: 'firstName', type: 'text', label: 'First Name' } },
30 | { key: 2, attr: { name: 'lastName', type: 'text', label: 'Last Name' } },
31 | { key: 3, attr: { name: 'email', type: 'email', label: 'Email' } },
32 | { key: 4, attr: { name: 'password', type: 'password', label: 'Password' } }
33 | ];
34 |
35 | static propTypes = {
36 | mutations: PropTypes.shape({
37 | signUp: PropTypes.func.isRequired
38 | }).isRequired,
39 | actions: PropTypes.shape({
40 | signIn: PropTypes.func.isRequired
41 | }).isRequired
42 | };
43 |
44 | state = {
45 | errors: {},
46 | serverErrors: {},
47 | touched: false
48 | };
49 |
50 | getServerErrors(err: { graphQLErrors?: Array<{ message: string }> }) {
51 | if (err.graphQLErrors) {
52 | const obj = {};
53 | obj.message = err.graphQLErrors[0].message;
54 | this.setState({
55 | serverErrors: obj
56 | });
57 | }
58 | }
59 |
60 | handleTouch = () => {
61 | this.setState({ touched: true });
62 | };
63 |
64 | handleChange = (e: SyntheticEvent) => {
65 | const fieldValue = e.currentTarget.value;
66 | const fieldName = e.currentTarget.name;
67 | const obj = {};
68 | obj[fieldName] = fieldValue;
69 | this.setState(obj);
70 | };
71 |
72 | handleSubmit(e: SyntheticEvent, valuesPack) {
73 | e.preventDefault();
74 |
75 | // reset state
76 | this.setState({
77 | errors: {},
78 | serverErrors: {}
79 | });
80 |
81 | const handleValidate = validate(valuesPack);
82 |
83 | if (handleValidate.touched) {
84 | this.setState({ touched: handleValidate.touched });
85 | }
86 | if (handleValidate.errors) {
87 | return this.setState({ errors: handleValidate.errors });
88 | }
89 |
90 | this.props.mutations
91 | .signUp(valuesPack)
92 | .then(
93 | (response: {
94 | data: {
95 | signinUser: { token: string },
96 | createUser: { errors: Object }
97 | }
98 | }) => {
99 | if (response.data.signinUser) {
100 | this.props.actions.signIn(response.data.signinUser.token);
101 | } else {
102 | this.setState({
103 | errors: response.data.createUser.errors
104 | });
105 | }
106 | }
107 | )
108 | .catch((err: { graphQLErrors?: Array<{ message: string }> }) => {
109 | this.getServerErrors(err);
110 | });
111 | }
112 |
113 | render() {
114 | const fields = this.formFields;
115 | // Packing all the necessary auth field states
116 | const valuesPack = {};
117 |
118 | fields.map(x => {
119 | const y: string = x.attr.name;
120 | if (this.state[y]) {
121 | valuesPack[y] = this.state[y];
122 | }
123 | return valuesPack;
124 | });
125 |
126 | return (
127 |
128 |
) => {
130 | this.handleSubmit(e, valuesPack);
131 | }}
132 | handleChange={this.handleChange}
133 | fields={fields}
134 | selectFields="signUpFields"
135 | errors={this.state.errors}
136 | touched={this.state.touched}
137 | handleTouch={this.handleTouch}
138 | />
139 |
140 |
141 | {Object.keys(this.state.errors).length === 0 &&
142 | this.state.serverErrors.message}
143 |
144 |
145 | );
146 | }
147 | }
148 |
149 | export default connect(SignUpForm);
150 |
--------------------------------------------------------------------------------
/components/SignUpForm/signupUser.gql:
--------------------------------------------------------------------------------
1 | mutation signupUser(
2 | $firstName: String!
3 | $lastName: String!
4 | $email: String!
5 | $password: String!
6 | ) {
7 | createUser(
8 | firstName: $firstName
9 | lastName: $lastName
10 | authProvider: { email: { email: $email, password: $password } }
11 | ) {
12 | id
13 | }
14 | signinUser(email: { email: $email, password: $password }) {
15 | token
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/components/SignUpForm/store.js:
--------------------------------------------------------------------------------
1 | import { graphql } from 'react-apollo';
2 | import { connect } from 'react-redux';
3 | import { dispatchers } from '../AuthFields/store';
4 | import createUserGql from './signupUser.gql';
5 |
6 | const withMutation = graphql(createUserGql, {
7 | props: ({ mutate }) => ({
8 | mutations: {
9 | signUp: ({ firstName, lastName, email, password }) =>
10 | mutate({
11 | variables: { firstName, lastName, email, password }
12 | })
13 | }
14 | })
15 | });
16 |
17 | const mapDispatchToProps = dispatch => ({
18 | actions: {
19 | signIn(token) {
20 | dispatch(dispatchers.signIn(token));
21 | }
22 | }
23 | });
24 |
25 | export default comp => {
26 | const compWithApollo = withMutation(comp);
27 | return connect(
28 | null,
29 | mapDispatchToProps
30 | )(compWithApollo);
31 | };
32 |
--------------------------------------------------------------------------------
/components/Theme.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const App = styled.div`
4 | background-color: ${props => props.theme.colors.background};
5 | color: ${props => props.theme.colors.text};
6 | `;
7 |
8 | export const A = styled.a`
9 | color: ${props => props.theme.colors.main};
10 |
11 | &:active,
12 | &:hover {
13 | text-decoration: underline;
14 | }
15 | `;
16 |
17 | export const P = styled.p`
18 | font-size: ${props => props.theme.font.sizes.normal};
19 | line-height: ${props => props.theme.font.sizes.bigger};
20 | `;
21 |
22 | export const Article = styled.article`
23 | margin: ${props => props.theme.alignment.horizontalcenter};
24 | max-width: 650px;
25 | `;
26 |
27 | export const Button = styled.button`
28 | align-items: center;
29 | background-color: ${props => props.theme.colors.main};
30 | border: 0;
31 | color: ${props => props.theme.colors.textAlt};
32 | display: flex;
33 | padding: ${props => props.theme.spacing.smaller};
34 | &:active {
35 | background-color: ${props =>
36 | props.theme
37 | .helper(props.theme.colors.main)
38 | .darken(0.2)
39 | .string()};
40 | transition: background-color 0.3s;
41 | }
42 | &:focus {
43 | outline: none;
44 | }
45 | `;
46 |
--------------------------------------------------------------------------------
/containers/Default.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import { withRouter } from 'next/router';
4 | import { Helmet } from 'react-helmet';
5 | import PropTypes from 'prop-types';
6 | import App from '../components/App';
7 | import Header from '../components/Header';
8 | import ProjectInfo from '../components/ProjectInfo';
9 | import NetworkStatus from '../components/NetworkStatus';
10 |
11 | type Props = {
12 | title?: string,
13 | router: Object,
14 | children: React.Element<*>
15 | };
16 |
17 | const Default = (props: Props) => (
18 |
19 |
20 |
21 | {props.title && props.title !== ''
22 | ? `${props.title} :: RAN! React . GraphQL . Next.js Toolkit`
23 | : 'RAN! React . GraphQL . Next.js Toolkit'}
24 |
25 |
26 |
27 |
28 | {props.children}
29 |
30 |
31 | );
32 |
33 | Default.propTypes = {
34 | title: PropTypes.string,
35 | router: PropTypes.object.isRequired,
36 | children: PropTypes.element.isRequired
37 | };
38 |
39 | Default.defaultProps = {
40 | title: ''
41 | };
42 |
43 | export default withRouter(Default);
44 |
--------------------------------------------------------------------------------
/docs/Architecture/app-and-fav-icons.md:
--------------------------------------------------------------------------------
1 | # RAN!
2 |
3 | ## App Icons and Favicon
4 |
5 | For rendering App Icons and favicon, RAN is using ```/components/AppIcons/index.js```. Basically, This component is pushing standard icon data to document.
6 |
7 | #### Change Icon Images
8 |
9 | You can copy your icon (with same names) to ```/static/icons/``` folder. It will start to use your icon immediately.
10 |
11 | #### Change the path of Icons
12 |
13 | You can make your changes on ```/components/AppIcons/index.js``` file. Every icons are coming from this component.
14 |
--------------------------------------------------------------------------------
/docs/Architecture/environment-variables.md:
--------------------------------------------------------------------------------
1 | # RAN!
2 |
3 | ## Environment Variables
4 |
5 | One of the best ways to use sensitive information is setting environment variables. Even in open source repositories without hard-coding the information within publicly available repositories, It's more important. Set your environment variable by using ```.env``` files, then you can use it on your application. On Node.js applications, [Dotenv](https://github.com/motdotla/dotenv) module is the easiest way to store/retrieve environment variables. It's a zero dependency module and simple to use. For access to these variables client-side [dotenv-webpack](https://github.com/mrsteele/dotenv-webpack) has been added which only exposes variables that are used by your front-end pages.
6 |
7 | #### How can we use?
8 | Just rename ".env-sample" file to ".env" to use your environment variables.
9 |
10 | #### Example
11 |
12 | **On The Server**
13 |
14 | Sample `.env` file
15 | ```
16 | PORT=3000
17 | DB_HOST=localhost
18 | DB_PASSWORD=password123
19 | ```
20 |
21 | On your application, you can access these values by using ```process.env```.
22 | ```javascript
23 | process.env.PORT
24 | process.env.DB_HOST
25 | process.env.DB_PASSWORD
26 | ```
27 |
28 | For more info, please [click here to check dotenv repo](https://github.com/motdotla/dotenv)
29 |
30 | **On The Client**
31 |
32 | Sample `public.env` file
33 | ```
34 | EMAIL=foo@bar.com
35 | ```
36 |
37 | On your application, you can access these values by using ```process.env```.
38 | ```javascript
39 | process.env.EMAIL
40 | ```
41 |
42 | On any of your client-side scripts, you can access your environment variables similarly
43 | ```javascript
44 | export default (props) => (
45 | My email is {process.env.EMAIL}
46 | );
47 | ```
48 |
49 | For more info, please [click here to check dotenv-webpack repo](https://github.com/mrsteele/dotenv-webpack)
50 |
51 | #### Tips
52 | - **Do not commit the ".env" file to any repository**, .gitignore file has a definition for that. If you commit, It can possibly create security flaw on your application.
53 | - If you are deploying to Zeit Now, in addition to dotenv, use [Now Secrets](https://zeit.co/docs/features/env-and-secrets). Zeit's OSS plan makes the source of your app available to the public and **sensitive information in ".env", "now.json" or other files may be exposed**.
54 |
55 | ### Shared Variables between Client and Server
56 | - Use `public.env` to store non-sensitive variables for the client. These variables will be used and exposed to the client so be sure they are safe to expose.
57 | - The variables in `public.env` and `.env` are merged and loaded on the server. Keep in mind that if a variable is defined in both files, `.env` variables will take precedence.
58 |
--------------------------------------------------------------------------------
/docs/Architecture/readme.md:
--------------------------------------------------------------------------------
1 | # RAN!
2 |
3 | ## Architecture \(WIP\)
4 |
5 | * [Environment Variables](environment-variables.md)
6 | * [App Icons and Favicon](app-and-fav-icons.md)
7 | * [Static Files \(Images, Docs, Others...\)](static-files.md)
8 | * [Static HTML Export](static-html-export.md)
9 | * [Type Checking](type-checking.md)
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/docs/Architecture/static-files.md:
--------------------------------------------------------------------------------
1 | # RAN!
2 |
3 | ## Static Files (Images, Docs, Others...)
4 | > This info is copied from Next.js Doc
5 | > [Doc Link](https://github.com/zeit/next.js/#static-file-serving-eg-images)
6 |
7 | From your code you can reference those files with /static/ URLs:
8 |
9 | ```js
10 | export default () => (
11 |
12 | )
13 | ```
14 |
--------------------------------------------------------------------------------
/docs/Architecture/static-html-export.md:
--------------------------------------------------------------------------------
1 | # RAN!
2 |
3 | ## Static HTML Export (WIP)
4 |
5 | It's prepared for deploying to some services such as Netlify, Github Pages, ...etc. Normally, You need to run Node.js server on your server to run your application. But with this way, You can also deploy and open your application without any Node.js server.
6 |
7 | #### Command
8 | ```yarn run build:static_export``` (or ```npm run build:static_export```)
9 |
10 | #### Limitations
11 | - If you are starting to use RAN! from beginning, My suggestion is you should run it with clean-project command. Otherwise, It can be hard to export it with example pages that uses cookie check or other page controls.
12 | - Unfortunately, you cannot use serverside cookie. We are using it on Auth of RAN! but It cannot work properly.
13 | - Dynamic routing doesn't work completely. If you want to add some routes that you want to get querystring/url data, You need to add these routes manually on ```/next.config.js```. But be careful.
14 |
15 | [https://github.com/zeit/next.js#limitation](For other limitations, Please click here to see Next.js static html export limitations)
16 |
--------------------------------------------------------------------------------
/docs/Architecture/type-checking.md:
--------------------------------------------------------------------------------
1 | # RAN!
2 |
3 | ## Type Checking
4 |
5 | For type checking, we are using [Flow](https://flow.org). But It's optional. The default configuration is to type check only on the files that have a
6 |
7 | ```js
8 | //@ flow
9 | ```
10 |
11 | as its first line.
12 |
13 | The configuration file is named .flowconfig and located at the root of the project. It has its default values as when created but with a folder called /types added to check if you have custom global types.
14 |
--------------------------------------------------------------------------------
/docs/Built_with_ran.md:
--------------------------------------------------------------------------------
1 | # RAN! Documentation
2 |
3 | ## Built with RAN!
4 |
5 | (WIP)
6 |
7 | > If you want to add your website here, please add that to [this topic on github](https://github.com/Sly777/ran/issues/414)
8 |
--------------------------------------------------------------------------------
/docs/Commands.md:
--------------------------------------------------------------------------------
1 | # RAN! Documentation
2 |
3 | ## Commands
4 |
5 | ### create:page
6 | _command:_ ```yarn run create:page``` (or ```npm run create:page```)
7 |
8 | It helps to create new page easiest way. After run this command, It will ask some questions and boom! It's ready.
9 |
10 | 
11 |
12 | ### create:route
13 | _command:_ ```yarn run create:route``` (or ```npm run create:route```)
14 |
15 | Create new route for your pages
16 |
17 | ### create:container
18 | _command:_ ```yarn run create:container``` (or ```npm run create:container```)
19 |
20 | Create new container
21 |
22 | ### create:component
23 | _command:_ ```yarn run create:component``` (or ```npm run create:component```)
24 |
25 | Create new component with options (such as *Style support*, *Store support*, *GraphQL support*)
26 |
27 | 
28 |
29 | ### lint
30 | _command:_ ```yarn run lint``` (or ```npm run lint```)
31 |
32 | Fix & show lint errors (and prettier infos) automatically
33 |
34 | ### lint:watch
35 | _command:_ ```yarn run lint:watch``` (or ```npm run lint:watch```)
36 |
37 | Watch the changes of graphQL files to show errors and warnings on eslint
38 | **Info:** Basically, There is watcher for js files if you run ```dev``` command. This one is additional command for graphql files.
39 |
40 | ### dev
41 | _command:_ ```yarn run dev``` (or ```npm run dev```)
42 |
43 | Run lint and then open the server on your local on development mode
44 |
45 | ### analyze
46 | _command:_ ```yarn run analyze``` (or ```npm run analyze```)
47 |
48 | Analyze the packages and files that you used on your app
49 |
50 | ### build
51 | _command:_ ```yarn run build``` (or ```npm run build```)
52 |
53 | Build the application for Production
54 |
55 | ### build:static_export
56 | _command:_ ```yarn run build:static_export``` (or ```npm run build:static_export```)
57 |
58 | Build and export your application by using Static HTML Export feature of Next.js. To see limitations, [Click here](/docs/Architecture/static-html-export.md)
59 |
60 | ### start
61 | _command:_ ```yarn run start``` (or ```npm run start```)
62 |
63 | Start the application for Production
64 |
65 | ### start:multicore
66 | _command:_ ```yarn run start:multicore``` (or ```npm run start:multicore```)
67 |
68 | Start the application for Production with multicore support (via PM2). You need to install PM2 globally.
69 |
70 | ### setup
71 | _command:_ ```yarn run setup``` (or ```npm run setup```)
72 |
73 | Prepare the application with example project
74 |
75 | ### setup:clean
76 | _command:_ ```yarn run setup:clean``` (or ```npm run setup:clean```)
77 |
78 | Prepare the application without example project. Recommended if you work on static application
79 |
80 | ## GraphQL Commands
81 |
82 | On these commands, RAN is using [graphql-config](https://github.com/graphcool/graphql-config), [graphql-cli](https://github.com/graphcool/graphql-cli), [graphql-voyager](https://github.com/APIs-guru/graphql-voyager) and [grapql-cli-voyager](https://github.com/graphcool/graphql-cli-voyager). The important thing is that you need to update your [/.graphqlconfig](/.graphqlconfig) file with your real graphql url to work with them. For details info how to work with ```.graphgqlconfig``` file, please check [graphql-config](https://github.com/graphcool/graphql-config).
83 |
84 | ##### **Important Info**
85 | Don't save secure information in ```.graphqlconfig``` file. Use [Environment variables](/docs/Architecture/environment-variables.md) for that. On RAN toolkit, We are using hard-coded example graphql url inside of that but It doesn't mean it's secure way.
86 |
87 | ### graphql:play
88 | _command:_ ```yarn run graphql:play``` (or ```npm run graphql:play```)
89 |
90 | It opens the browser to play (or work :) on your graphql server
91 |
92 | 
93 |
94 | ### graphql:update_schema
95 | _command:_ ```yarn run graphql:update_schema``` (or ```npm run graphql:update_schema```)
96 |
97 | Updates your local schema file with updated one from server
98 |
99 | ### graphql:see_graph
100 | _command:_ ```yarn run graphql:see_graph``` (or ```npm run graphql:see_graph```)
101 |
102 | See your graphQL API as a interactive graph
103 |
--------------------------------------------------------------------------------
/docs/Contributing.md:
--------------------------------------------------------------------------------
1 | # RAN! Documentation
2 |
3 | ## Contributing
4 |
5 | When contributing to this repository, please first discuss the change you wish to make via issue,
6 | email, or any other method with the owners of this repository before making a change.
7 |
8 | Please note we have a code of conduct, please follow it in all your interactions with the project.
9 |
10 | ## Pull Request Process
11 |
12 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a
13 | build.
14 | 2. Update the README.md with details of changes to the interface, this includes new environment
15 | variables, exposed ports, useful file locations and container parameters.
16 | 3. Increase the version numbers in any examples files and the README.md to the new version that this
17 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
18 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
19 | do not have permission to do that, you may request the second reviewer to merge it for you.
20 |
21 | ## Credits
22 | This project exists thanks to all the contributors. Thank you.
23 |
24 |
25 |
26 | ### Backers
27 |
28 | Thank you to all our backers! [[Become a backer](https://opencollective.com/ran#backer)]
29 |
30 |
31 |
32 |
33 | ### Sponsors
34 |
35 | Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/ran#sponsor))
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/docs/Deployment/README.md:
--------------------------------------------------------------------------------
1 | # RAN!
2 |
3 | ## Deployment
4 |
5 | - [With now](with-now.md)
6 | - [Amazon - AWS](aws.md)
7 | - [Heroku](heroku.md)
8 | - [Digital Ocean](digital-ocean.md)
9 |
--------------------------------------------------------------------------------
/docs/Deployment/aws.md:
--------------------------------------------------------------------------------
1 | # RAN! Documentation
2 |
3 | ## Deployment
4 | ### Amazon (AWS)
5 |
6 | #### AWS Beanstalk
7 | * remove "prestart" from package.json. Beanstalk individual instances will run prestart which triggers a
8 | build and generates a new BUILD_ID. It is important that build happens before AWS on local or CI and result is pushed
9 | to Beanstalk so that you can load balance multiple instances of the app each containing the same prebuilt .next/BUILD_ID.
10 |
11 | * `npm run zip-for-beanstalk` will remove all devDependencies and then zip up the folder. Note
12 | (You will need to run npm install again after this. You could consider repack-zip if you want to build locally
13 | without blowing out your node_modules directory)
14 |
15 | * create AWS Beanstalk application. An application is a grouping of environments
16 | (ex: myapp-prod, myapp-test, myapp-beta, myapp-devsandbox).
17 |
18 | Create environment. Amount of settings can be overwhelming at first but most will become obvious defaults once you learn them.
19 | Take the time to understand each one. Become comfortable creating, cloning, destroying environments.
20 |
21 | * environment type "Web".
22 | * Configuration > Software
23 | - Node.js version:
24 | - Node command: node server.js
25 | - environment variables: PORT=8081, NODE_ENV=production
26 | * Configuration > Instances
27 | - EC2 Instance Type: t2.micro (at time of writing it defaults to t1 which is old)
28 | * Configuration > Load balancer
29 | - Application (not classic) recommended to handle http2, https, http->https redirection
30 | - stickiness enabled (when updates are going you don't want them to hit different version server)
31 | * Configuration > Security
32 | - First create security groups in EC2 representing each env (ex: myapp-test). You will set firewall ports to this group and
33 | may want different settings for test and production.
34 | - Recommended to set key pair so you can ssh in later if you must
35 | (note that machines should be treated as ephemeral as amazon will replace them)
36 | * Configuration > Database
37 | - Never ever setup database inside beanstalk (it will be destroyed if you need to rebuild environment)
38 | Create database in RDS if you need one.
39 |
40 | * upload zip file to AWS Beanstalk manually at the dashboard or use AWS cli during local or CI flows.
41 |
42 | # Quicker sandbox builds
43 | Consider setting Configuration > Monitoring > Health reporting to basic on sandbox environments for maximum speed.
44 | The enhanced reporting takes a long time to come back successful which can be unnecessary friction on R&D server.
45 |
46 | # build changes that conflict with configuration settings
47 | It can be helpful to set Configuration > Rolling updates and deployments > Deployment preferences >
48 | ignore health check to "don't fail" and healthy threshold to Severe when first testing so beanstalk wont roll back builds.
49 | This will give you easier tweaking so you can reboot with different settings. Just don't forget to set appropriate
50 | production settings when you get a stable build going.
51 |
52 |
53 | #### AWS Lambda (serverless)
54 | Cautionary note that Lambda isn't really made for consumer facing sites.
55 | Infrastructure (cloudfront & api gateway )adds ~100ms minimum latency, CPU response between 0 and 2s depending
56 | on how your request is prioritized, much longer startup delay for each concurrent request to warm up.
57 | If you would like to proceed though, this is a great example: https://github.com/skriems/next-material
58 |
--------------------------------------------------------------------------------
/docs/Deployment/digital-ocean.md:
--------------------------------------------------------------------------------
1 | # RAN! Documentation
2 |
3 | ## Deployment
4 | ### Digital Ocean
5 |
6 | [Click here to see Digital Ocean Tutorial - How To Set Up a Node.js Application for Production on Ubuntu 16.04](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-16-04)
7 |
8 | RAN! has one script to prepare the server for Node.JS on [/helper_scripts/prepare-server-on-digitalocean.sh](/helper_scripts/prepare-server-on-digitalocean.sh)
9 |
10 | But before that, you need to follow this;
11 |
12 | - Create droplet with Ubuntu
13 | - Clone your Git repo
14 | - Then, run ```./helper_scripts/prepare-server-on-digitalocean.sh``` on Server Console
15 | - It will install nodejs and PM2
16 | - Then, run ```yarn && yarn run build``` to prepare packages & build
17 | - Finally, run ```pm2 start npm -- start:multicore``` to start server
18 |
--------------------------------------------------------------------------------
/docs/Deployment/heroku.md:
--------------------------------------------------------------------------------
1 | # RAN! Documentation
2 |
3 | ## Deployment
4 | ### Heroku
5 |
6 | [Click here to see Heroku Tutorial - Getting Started with Node.JS](https://devcenter.heroku.com/articles/getting-started-with-nodejs#set-up)
7 |
8 | Basically, RAN! is ready for Heroku Deployment. Just follow the structure on tutorial.
9 |
10 | - Firstly, run ```heroku create```
11 | - Then commit your changes ```git commit -m 'commit message'```
12 | - After commit, push your changes to heroku GIT ```git push heroku master```
13 | - That's all!
14 | - to scale your server, just run ```heroku ps:scale web=1```
15 |
--------------------------------------------------------------------------------
/docs/Deployment/with-now.md:
--------------------------------------------------------------------------------
1 | # RAN! Documentation
2 |
3 | ## Deployment
4 | ### With [now](https://zeit.co/now)
5 |
6 | If you still don't install **now**, please ([download from here](https://zeit.co/download))
7 |
8 | Just run this command:
9 |
10 | ```bash
11 | now
12 | ```
13 |
--------------------------------------------------------------------------------
/docs/Example.md:
--------------------------------------------------------------------------------
1 | 
2 | ### RAN : React . GraphQL . Next.js Toolkit
3 |
4 | ## Example
5 |
6 | **[Click here](https://ran.now.sh/)** to see example project to understand how RAN! works on production. I used **[graph.cool](https://www.graph.cool/)** service for GraphQL and **[now](https://zeit.co/now)** for hosting in the example.
7 |
--------------------------------------------------------------------------------
/docs/FAQ.md:
--------------------------------------------------------------------------------
1 | # RAN! Documentation
2 |
3 | ## FAQ
4 |
5 | #### GraphQL
6 |
7 | - _I have graphQL endpoint for my project. How can I use it?_
8 |
9 | Just change the URL on [/libraries/apolloClient.js](/libraries/apolloClient.js) line:6
10 |
11 | - _What is GraphQL?_
12 |
13 | - [http://graphql.org/](http://graphql.org/)
14 | - [https://www.quora.com/What-is-GraphQL](https://www.quora.com/What-is-GraphQL)
15 | - [https://www.pluralsight.com/courses/graphql-scalable-apis](https://www.pluralsight.com/courses/graphql-scalable-apis)
16 |
17 | - _Which GraphQL service should I use?_
18 |
19 | I always use [GraphCool](https://www.graph.cool) for GraphQL service. It has really easy-to-use UI and customer support is amazing. Also there is [Scaphold.io](https://scaphold.io). All of them can work with **RAN!**
20 |
21 | - _I want to use my own GraphQL server. Is it possible?_
22 |
23 | Yes! You just need to change graphQL url with your service URL and that's all.
24 |
25 | #### Node.JS
26 |
27 | - _What is Next.js?_
28 |
29 | - [https://zeit.co/blog/next2](https://zeit.co/blog/next2)
30 | - [https://github.com/zeit/next.js/](https://github.com/zeit/next.js/)
31 | - [https://scotch.io/tutorials/react-universal-with-next-js-server-side-react](https://scotch.io/tutorials/react-universal-with-next-js-server-side-react)
32 |
33 | - _Why do you use Express?_
34 |
35 | You can use also HAPI and Koa. [Click here for details](https://github.com/zeit/next.js/#custom-server-and-routing)
36 |
37 | #### Styling (CSS)
38 |
39 | - _What is CSS-in-JS?_
40 |
41 | - [css-in-javascript-the-future-of-component-based-styling-70b161a79a32](https://medium.freecodecamp.com/css-in-javascript-the-future-of-component-based-styling-70b161a79a32)
42 | - [css-in-js-the-argument-refined-471c7eb83955](https://medium.com/@steida/css-in-js-the-argument-refined-471c7eb83955)
43 | - [stop-using-css-in-javascript-for-web-development-fa32fb873dcc](https://medium.com/@gajus/stop-using-css-in-javascript-for-web-development-fa32fb873dcc)
44 |
45 | - _What is Styled-Components?_
46 |
47 | - [https://www.styled-components.com/](https://www.styled-components.com/)
48 | - [https://github.com/styled-components/styled-components](https://github.com/styled-components/styled-components)
49 | - [a-5-minute-intro-to-styled-components-41f40eb7cd55](https://medium.freecodecamp.com/a-5-minute-intro-to-styled-components-41f40eb7cd55)
50 | - [styled-components-enforcing-best-practices-component-based-systems/](https://www.smashingmagazine.com/2017/01/styled-components-enforcing-best-practices-component-based-systems/)
51 |
52 | #### Deployment
53 |
54 | - _I want to deploy my app to XXXXX service. How can I do that?_
55 |
56 | Mostly, It should work if you follow [this way](/docs/Deployment/digital-ocean.md). If It doesn't work, You can create Issue on Github or search on Stack Overflow.
57 |
--------------------------------------------------------------------------------
/docs/Helper-scripts.md:
--------------------------------------------------------------------------------
1 | # RAN! Documentation
2 |
3 | ## Helper Scripts
4 |
5 | - ***http.nginx.conf***
6 | WIP
7 |
8 | - ***https.nginx.conf***
9 | WIP
10 |
11 | - ***run-development.sh***
12 | WIP
13 |
14 | - ***run-production.sh***
15 | WIP
16 |
--------------------------------------------------------------------------------
/docs/Offline.md:
--------------------------------------------------------------------------------
1 | # RAN! Documentation
2 |
3 | ## Offline Support (WIP)
4 |
5 | There is experimental offline support on RAN!. But you should be careful when you are using it.
6 |
7 | To activate; you need to add ```OFFLINE_SUPPORT=true``` to your ENV. Then It will work on Production (not Development).
8 |
--------------------------------------------------------------------------------
/docs/Packages.md:
--------------------------------------------------------------------------------
1 | # RAN! Documentation
2 |
3 | ## Packages
4 |
5 | * [React]()
6 | * [Apollo]()
7 | * [Next.JS]()
8 | * [Express]()
9 | * [Redux]()
10 | * [GraphQL]()
11 | * [Styled Components]()
12 | * [Eslint]()
13 | * [Prettier]()
14 | * [Flow](https://flow.org)
15 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # RAN!
2 |
3 | ## Documentation
4 |
5 | ## Table of Contents
6 |
7 | * [Deployment](Deployment)
8 |
9 | * [NOW](Deployment/with-now.md)
10 |
11 | * [Amazon - AWS](Deployment/aws.md)
12 |
13 | * [Heroku](Deployment/heroku.md)
14 |
15 | * [Digital Ocean](Deployment/digital-ocean.md)
16 |
17 | * [Architecture](Architecture)
18 |
19 | * [Environment Variables](Architecture/environment-variables.md)
20 |
21 | * [App Icons and Favicon](Architecture/app-and-fav-icons.md)
22 |
23 | * [Static Files \(Images, Docs, Others...\)](Architecture/static-files.md)
24 |
25 | * [Static HTML Export](Architecture/static-html-export.md)
26 |
27 | * [Type Checking](Architecture/type-checking.md)
28 |
29 | * [Packages](Packages.md)
30 |
31 | * [Commands](Commands.md)
32 |
33 | * [Styling](Styling.md)
34 |
35 | * [Routing](Routing.md)
36 |
37 | * [Offline Support](Offline.md)
38 |
39 | * [Helper Scripts](Helper-scripts.md)
40 |
41 | * [Example](Example.md)
42 |
43 | * [Roadmap](Roadmap.md)
44 |
45 | * [Contributing](Contributing.md)
46 |
47 | * [FAQ](FAQ.md)
48 |
49 | * [Built with RAN!](Built_with_ran.md)
50 |
51 | * [Support](Support.md)
52 |
--------------------------------------------------------------------------------
/docs/Roadmap.md:
--------------------------------------------------------------------------------
1 | # RAN! Documentation
2 |
3 | ## Roadmap
4 |
5 | - [ ] Improve Documentation
6 | - [ ] CL Commands (Add/remove routes, Remove pages, Add/remove addons)
7 | - [ ] Offline Support (WIP)
8 | - [ ] Example for using NGINX
9 | - [ ] Example for using Style Themes
10 | - [ ] Improve Performance More
11 | - [ ] Docker Support
12 | - [ ] More Deployment Examples
13 |
--------------------------------------------------------------------------------
/docs/Routing.md:
--------------------------------------------------------------------------------
1 | # RAN! Documentation
2 |
3 | ## Routing
4 |
5 | Because of awesome Next.js, You don't need to add routes for all pages. Every file on Pages folder basically has route as they named.
6 |
7 | ```
8 | pages/index.js => /
9 | pages/about.js => /about
10 | pages/a.js => /a
11 | pages/b.js => /b
12 | ...etc.
13 | ```
14 |
15 | If you want to change url (for **SEO** or put different path or getting data from URL), please add your route to [/routes.js](/routes.js).
16 |
--------------------------------------------------------------------------------
/docs/Styling.md:
--------------------------------------------------------------------------------
1 | # RAN! Documentation
2 |
3 | ## Styling
4 |
5 | 
6 |
7 | RAN! is using theme system of wondrous [Styled Components](https://www.styled-components.com/) library for styling app (css-in-js). [Click here for details](https://www.styled-components.com/docs/advanced#theming)
8 |
9 | There is basic theme component ([/libraries/theme.js](/libraries/theme.js)) on RAN!. You can access all theme props by using ```props.theme``` on your styling. Also there is helper for color manupulation as you can access that by using ```props.theme.helper```. On this prop, RAN! is using [color.js](https://github.com/Qix-/color) that has support for most important color manipulation functions.
10 |
11 | For now, there are three themes (***main***, ***inverted***, ***eightbit***) but You can add how many you want!
12 |
13 | #### Using theme
14 |
15 | Basically, RAN! is using ```main``` theme on all pages. But to change this. You need to add ```theme``` prop to `````` component on every page.
16 |
17 | Example:
18 | ```js
19 |
20 | Hello World
21 |
22 | ```
23 |
24 | To change the theme on all pages, You can set theme name on [/components/App.js](/components/App.js):10.line.
25 |
26 | #### Create New Theme
27 |
28 | There are two options for this. Firstly, You can create new theme object on [/libraries/theme.js](/libraries/theme.js).
29 |
30 | ```js
31 | themeList.NEWTHEMENAME = {
32 | font: {
33 | sizes: {
34 | normal: '14px',
35 | big: '15px'
36 | },
37 | colors: {
38 | main: '#22BAD9',
39 | success: '#5cb85c',
40 | warn: '#ffc067'
41 | }
42 | };
43 |
44 | ```
45 |
46 | or you can extend any theme that you have on [/libraries/theme.js](/libraries/theme.js).
47 |
48 | ```js
49 | themeList.NEWTHEMENAME = themeList.extend('main', {
50 | colors: {
51 | main: '#40337f',
52 | success: '#1bcb01',
53 | error: '#722640',
54 | background: '#000000',
55 | text: '#ffffff'
56 | },
57 | font: {
58 | family: {
59 | normal: 'Consolas, monaco, monospace'
60 | }
61 | }
62 | });
63 | ```
64 |
--------------------------------------------------------------------------------
/docs/Support.md:
--------------------------------------------------------------------------------
1 | 
2 | ### RAN : React . GraphQL . Next.js Toolkit
3 |
4 | ## Support
5 |
6 | If you have any problem/idea/opinion/error/wish, Please don't hesitate to contact me via [Issues on GitHub](https://github.com/Sly777/ran/issues), [Gitter RAN Channel](https://gitter.im/ran-boilerplate/Lobby) or [E-mail](mailto:info@rantoolkit.com). I can try to help as soon as possible.
7 |
--------------------------------------------------------------------------------
/helper_scripts/CL_commands/__helpers.js:
--------------------------------------------------------------------------------
1 | const figlet = require('figlet');
2 | const chalk = require('chalk');
3 | const path = require('path');
4 | const fs = require('fs');
5 | const handlebars = require('handlebars');
6 | const replace = require('replace-in-file');
7 | const router = require('../../routes');
8 |
9 | const modules = {};
10 |
11 | modules.config = {
12 | appDir: './',
13 | pagesDir: './pages',
14 | componentsDir: './components',
15 | containersDir: './containers',
16 | librariesDir: './libraries',
17 | templatesDir: './helper_scripts/templates',
18 | routeFile: './routes.js',
19 | serverFile: './server.js'
20 | };
21 |
22 | modules.writeRan = function writeRan(callback) {
23 | chalk.yellow(
24 | figlet.text(
25 | 'RAN!',
26 | {
27 | verticalLayout: 'full'
28 | },
29 | (err, data) => {
30 | process.stdout.write('\n');
31 | process.stdout.write(data);
32 | process.stdout.write('\n');
33 | if (callback) callback();
34 | }
35 | )
36 | );
37 | };
38 |
39 | modules.isUsedOnDir = function isUsedOnDir(
40 | startPath,
41 | filter,
42 | onlyDirectories = false
43 | ) {
44 | if (!fs.existsSync(startPath)) {
45 | return false;
46 | }
47 |
48 | if (onlyDirectories) {
49 | return fs.existsSync(`${startPath}/${filter}`);
50 | }
51 |
52 | const files = fs.readdirSync(startPath);
53 | let isFound = false;
54 |
55 | for (let i = 0; i < files.length; i += 1) {
56 | const filename = path.join(startPath, files[i]);
57 | const stat = fs.lstatSync(filename);
58 | if (stat.isDirectory()) {
59 | isUsedOnDir(filename, filter); // recurse
60 | } else if (filename.indexOf(filter) >= 0) {
61 | isFound = true;
62 | }
63 | }
64 |
65 | return isFound;
66 | };
67 |
68 | modules.getFilesOnDir = function getFilesOnDir(startPath) {
69 | if (!fs.existsSync(startPath)) {
70 | return [];
71 | }
72 |
73 | const files = fs.readdirSync(startPath);
74 | const isFound = [];
75 |
76 | for (let i = 0; i < files.length; i += 1) {
77 | const filename = path.join(startPath, files[i]);
78 | const stat = fs.lstatSync(filename);
79 | if (stat.isDirectory()) {
80 | isFound.concat(getFilesOnDir(filename)); // recurse
81 | } else {
82 | const pagename = files[i].replace('.js', '');
83 | if (pagename !== '_document') isFound.push(pagename);
84 | }
85 | }
86 |
87 | return isFound;
88 | };
89 |
90 | modules.isUsedOnRoutes = function isUsedOnRoutes(url) {
91 | let isFound = false;
92 | router.routes.forEach(route => {
93 | if (route.pattern.indexOf(url) !== -1) {
94 | isFound = true;
95 | }
96 | });
97 | return isFound;
98 | };
99 |
100 | modules.getTempfromHandlebar = function getTempfromHandlebar(
101 | tempPath,
102 | data,
103 | callback
104 | ) {
105 | fs.readFile(tempPath, 'utf-8', (err, source) => {
106 | if (err) throw err;
107 | const template = handlebars.compile(source);
108 | const exportCode = template(data);
109 |
110 | callback(exportCode);
111 | });
112 | };
113 |
114 | modules.addTexttoFile = function addTexttoFile(
115 | filePath,
116 | from,
117 | text,
118 | cb,
119 | before = true
120 | ) {
121 | const matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g;
122 | const re = new RegExp(from.replace(matchOperatorsRe, '\\$&'));
123 |
124 | replace(
125 | {
126 | encoding: 'utf8',
127 | files: filePath,
128 | from: re,
129 | to: before ? `${text}${from}` : `${from}\n${text}`
130 | },
131 | error => {
132 | if (error) {
133 | throw error;
134 | }
135 | cb();
136 | }
137 | );
138 | };
139 |
140 | modules.createPageFromTemplate = function createPageFromTemplate(
141 | filename,
142 | callback
143 | ) {
144 | modules.getTempfromHandlebar(
145 | `${modules.config.templatesDir}/page.hbs`,
146 | {
147 | filename
148 | },
149 | code => {
150 | fs.writeFile(
151 | `${modules.config.pagesDir}/${filename}.js`,
152 | code,
153 | { flag: 'wx' },
154 | _err => {
155 | if (_err) throw _err;
156 |
157 | callback();
158 | }
159 | );
160 | }
161 | );
162 | };
163 |
164 | modules.createContainerFromTemplate = function createContainerFromTemplate(
165 | filename,
166 | callback
167 | ) {
168 | modules.getTempfromHandlebar(
169 | `${modules.config.templatesDir}/container.hbs`,
170 | {
171 | filename
172 | },
173 | code => {
174 | fs.writeFile(
175 | `${modules.config.containersDir}/${filename}.js`,
176 | code,
177 | { flag: 'w' },
178 | _err => {
179 | if (_err) throw _err;
180 |
181 | callback();
182 | }
183 | );
184 | }
185 | );
186 | };
187 |
188 | modules.createComponentFromTemplate = function createComponentFromTemplate(
189 | options,
190 | callback = () => {}
191 | ) {
192 | modules.getTempfromHandlebar(
193 | `${modules.config.templatesDir}/component.hbs`,
194 | options,
195 | code => {
196 | if (
197 | !fs.existsSync(`${modules.config.componentsDir}/${options.filename}`)
198 | ) {
199 | fs.mkdirSync(`${modules.config.componentsDir}/${options.filename}`);
200 | }
201 |
202 | fs.writeFile(
203 | `${modules.config.componentsDir}/${options.filename}/index.js`,
204 | code,
205 | { flag: 'wx' },
206 | _err => {
207 | if (_err) throw _err;
208 |
209 | callback();
210 | }
211 | );
212 | }
213 | );
214 | };
215 |
216 | modules.createStoreFromTemplate = function createStoreFromTemplate(
217 | options,
218 | callback = () => {}
219 | ) {
220 | modules.getTempfromHandlebar(
221 | `${modules.config.templatesDir}/component_store.hbs`,
222 | options,
223 | code => {
224 | fs.writeFile(
225 | `${modules.config.componentsDir}/${options.filename}/store.js`,
226 | code,
227 | { flag: 'wx' },
228 | _err => {
229 | if (_err) throw _err;
230 |
231 | callback();
232 | }
233 | );
234 | }
235 | );
236 | };
237 |
238 | modules.createStyleFromTemplate = function createStyleFromTemplate(
239 | options,
240 | callback = () => {}
241 | ) {
242 | modules.getTempfromHandlebar(
243 | `${modules.config.templatesDir}/component_style.hbs`,
244 | options,
245 | code => {
246 | fs.writeFile(
247 | `${modules.config.componentsDir}/${options.filename}/styles.js`,
248 | code,
249 | { flag: 'wx' },
250 | _err => {
251 | if (_err) throw _err;
252 |
253 | callback();
254 | }
255 | );
256 | }
257 | );
258 | };
259 |
260 | modules.createGraphqlFromTemplate = function createGraphqlFromTemplate(
261 | options,
262 | callback = () => {}
263 | ) {
264 | modules.getTempfromHandlebar(
265 | `${modules.config.templatesDir}/component_graphql.hbs`,
266 | options,
267 | code => {
268 | fs.writeFile(
269 | `${modules.config.componentsDir}/${options.filename}/${
270 | options.graphqlName
271 | }.gql`,
272 | code,
273 | { flag: 'wx' },
274 | _err => {
275 | if (_err) throw _err;
276 |
277 | callback();
278 | }
279 | );
280 | }
281 | );
282 | };
283 |
284 | modules.clearReducerList = function clearReducerList(callback) {
285 | modules.getTempfromHandlebar(
286 | `${modules.config.templatesDir}/reducer.hbs`,
287 | {},
288 | code => {
289 | fs.writeFile(
290 | `${modules.config.librariesDir}/reducer.js`,
291 | code,
292 | { flag: 'w' },
293 | _err => {
294 | if (_err) throw _err;
295 |
296 | callback();
297 | }
298 | );
299 | }
300 | );
301 | };
302 |
303 | modules.clearRoutes = function clearRoutes(callback) {
304 | const re = /^(routes.)[A-Za-z(', /:);_]*/gm;
305 |
306 | replace(
307 | {
308 | encoding: 'utf8',
309 | files: `${modules.config.routeFile}`,
310 | from: re,
311 | to: ''
312 | },
313 | error => {
314 | if (error) {
315 | throw error;
316 | }
317 | callback();
318 | }
319 | );
320 | };
321 |
322 | modules.updateReadme = function updateReadme(projectname, callback) {
323 | modules.getTempfromHandlebar(
324 | `${modules.config.templatesDir}/readme.hbs`,
325 | {
326 | projectname
327 | },
328 | code => {
329 | fs.writeFile(
330 | `${modules.config.appDir}README.md`,
331 | code,
332 | { flag: 'wx' },
333 | _err => {
334 | if (_err) throw _err;
335 |
336 | callback();
337 | }
338 | );
339 | }
340 | );
341 | };
342 |
343 | module.exports = modules;
344 |
--------------------------------------------------------------------------------
/helper_scripts/CL_commands/create_component.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const inquirer = require('inquirer');
4 | const helper = require('./__helpers');
5 |
6 | process.stdin.resume();
7 | process.stdin.setEncoding('utf8');
8 |
9 | function afterPageCreation(filename) {
10 | process.stdout.write('\n');
11 | process.stdout.write(`New Component ${filename} is created and ready!`);
12 | process.stdout.write('\n');
13 | process.stdout.write('\n');
14 | process.exit(0);
15 | }
16 |
17 | function askQuestions() {
18 | const questions = [
19 | {
20 | name: 'filename',
21 | type: 'input',
22 | message: 'Enter component name: (without whitespace)',
23 | validate(value) {
24 | if (value.length) {
25 | if (
26 | helper.isUsedOnDir(
27 | helper.config.componentsDir,
28 | value.indexOf('.js') > 0 ? value.replace('.js', '') : value,
29 | true
30 | )
31 | ) {
32 | return "It's already added. Please enter new component name.";
33 | }
34 | return true;
35 | }
36 | return 'It cannot be empty. Please enter it correctly...';
37 | }
38 | },
39 | {
40 | name: 'haveStore',
41 | type: 'confirm',
42 | message: () => 'Do you want to use store?',
43 | default: false
44 | },
45 | {
46 | name: 'haveGraphql',
47 | type: 'confirm',
48 | when: ({ haveStore }) => haveStore,
49 | message: () => 'Do you want to use graphql on this store?',
50 | default: false
51 | },
52 | {
53 | name: 'graphqlName',
54 | type: 'input',
55 | message: 'Enter graphql file name:',
56 | when: ({ haveGraphql }) => haveGraphql,
57 | validate(value) {
58 | if (value.length) {
59 | return true;
60 | }
61 | return 'It cannot be empty. Please enter it correctly...';
62 | }
63 | },
64 | {
65 | name: 'haveStyle',
66 | type: 'confirm',
67 | message: () => 'Do you want to use style?',
68 | default: false
69 | }
70 | ];
71 |
72 | inquirer.prompt(questions).then(answers => {
73 | // eslint-disable-next-line no-param-reassign
74 | answers.filename = answers.filename.replace(/\b\w/g, l => l.toUpperCase());
75 | helper.createComponentFromTemplate(answers, () => {
76 | if (answers.haveStore) {
77 | helper.createStoreFromTemplate(answers);
78 |
79 | if (answers.haveGraphql) {
80 | helper.createGraphqlFromTemplate(answers);
81 | }
82 | }
83 |
84 | if (answers.haveStyle) {
85 | helper.createStyleFromTemplate(answers);
86 | }
87 |
88 | setTimeout(() => {
89 | afterPageCreation(answers.filename);
90 | }, 1250);
91 | });
92 | });
93 | }
94 |
95 | helper.writeRan(askQuestions);
96 |
--------------------------------------------------------------------------------
/helper_scripts/CL_commands/create_container.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const inquirer = require('inquirer');
4 | const helper = require('./__helpers');
5 |
6 | process.stdin.resume();
7 | process.stdin.setEncoding('utf8');
8 |
9 | function afterContainerCreation(filename) {
10 | process.stdout.write('\n');
11 | process.stdout.write(`New Container ${filename} is created and ready!`);
12 | process.stdout.write('\n');
13 | process.stdout.write('\n');
14 | process.exit(0);
15 | }
16 |
17 | function askQuestions() {
18 | const questions = [
19 | {
20 | name: 'filename',
21 | type: 'input',
22 | message: 'Enter container name: (without whitespace)',
23 | validate(value) {
24 | if (value.length) {
25 | if (
26 | helper.isUsedOnDir(
27 | helper.config.containersDir,
28 | value.indexOf('.js') > 0 ? value : `${value}.js`
29 | )
30 | ) {
31 | return "It's already added. Please enter new container name.";
32 | }
33 | return true;
34 | }
35 | return 'It cannot be empty. Please enter it correctly...';
36 | }
37 | }
38 | ];
39 |
40 | inquirer.prompt(questions).then(({ filename }) => {
41 | helper.createContainerFromTemplate(filename, () => {
42 | afterContainerCreation(filename);
43 | });
44 | });
45 | }
46 |
47 | helper.writeRan(askQuestions);
48 |
--------------------------------------------------------------------------------
/helper_scripts/CL_commands/create_page.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const inquirer = require('inquirer');
4 | const helper = require('./__helpers');
5 |
6 | process.stdin.resume();
7 | process.stdin.setEncoding('utf8');
8 |
9 | function afterPageCreation(filename, prettyurl = null) {
10 | process.stdout.write('\n');
11 | process.stdout.write(`New Page ${filename} is created and ready!`);
12 | process.stdout.write('\n');
13 | if (prettyurl) {
14 | helper.getTempfromHandlebar(
15 | `${helper.config.templatesDir}/route.hbs`,
16 | {
17 | filename,
18 | prettyurl
19 | },
20 | code => {
21 | helper.addTexttoFile(
22 | helper.config.routeFile,
23 | '// @RANEndRoutes',
24 | code,
25 | () => {
26 | process.stdout.write('\n');
27 | process.stdout.write(
28 | `New Page ${filename} is added to "./routes.js" and ready!`
29 | );
30 | process.stdout.write('\n');
31 | process.stdout.write('\n');
32 | process.exit(0);
33 | }
34 | );
35 | }
36 | );
37 | }
38 | }
39 |
40 | function askQuestions() {
41 | const questions = [
42 | {
43 | name: 'filename',
44 | type: 'input',
45 | message: 'Enter page name: (without whitespace)',
46 | validate(value) {
47 | if (value.length) {
48 | if (
49 | helper.isUsedOnDir(
50 | helper.config.pagesDir,
51 | value.indexOf('.js') > 0 ? value : `${value}.js`
52 | )
53 | ) {
54 | return "It's already added. Please enter new page name.";
55 | }
56 | return true;
57 | }
58 | return 'It cannot be empty. Please enter it correctly...';
59 | }
60 | },
61 | {
62 | name: 'isPretty',
63 | type: 'confirm',
64 | message: ({ filename }) =>
65 | `Do you want different URL? (current: /${filename})`,
66 | default: false
67 | },
68 | {
69 | name: 'prettyurl',
70 | type: 'input',
71 | message: 'Enter new URL:',
72 | when: ({ isPretty }) => isPretty,
73 | validate(value) {
74 | if (value.length) {
75 | if (helper.isUsedOnRoutes(value)) {
76 | return "It's already added. Please enter new URL.";
77 | }
78 | return true;
79 | }
80 | return 'It cannot be empty. Please enter it correctly...';
81 | }
82 | }
83 | ];
84 |
85 | inquirer.prompt(questions).then(({ filename, prettyurl = null }) => {
86 | helper.createPageFromTemplate(filename, () => {
87 | afterPageCreation(filename, prettyurl);
88 | });
89 | });
90 | }
91 |
92 | helper.writeRan(askQuestions);
93 |
--------------------------------------------------------------------------------
/helper_scripts/CL_commands/create_route.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const inquirer = require('inquirer');
4 | const helper = require('./__helpers');
5 |
6 | process.stdin.resume();
7 | process.stdin.setEncoding('utf8');
8 |
9 | function afterPageCreation(filename, prettyurl = null) {
10 | helper.getTempfromHandlebar(
11 | `${helper.config.templatesDir}/route.hbs`,
12 | {
13 | filename,
14 | prettyurl
15 | },
16 | code => {
17 | helper.addTexttoFile(
18 | helper.config.routeFile,
19 | '// @RANEndRoutes',
20 | code,
21 | () => {
22 | process.stdout.write('\n');
23 | process.stdout.write(
24 | `New Page ${filename} is added to "./routes.js" and ready!`
25 | );
26 | process.stdout.write('\n');
27 | process.stdout.write('\n');
28 | process.exit(0);
29 | }
30 | );
31 | }
32 | );
33 | }
34 |
35 | function askQuestions() {
36 | const questions = [
37 | {
38 | name: 'filename',
39 | type: 'list',
40 | message: 'Which page do you want to use:',
41 | choices: () => helper.getFilesOnDir(helper.config.pagesDir)
42 | },
43 | {
44 | name: 'isPretty',
45 | type: 'confirm',
46 | message: ({ filename }) =>
47 | `Do you want different URL? (current: /${filename})`,
48 | default: false
49 | },
50 | {
51 | name: 'prettyurl',
52 | type: 'input',
53 | message: 'Enter new URL:',
54 | when: ({ isPretty }) => isPretty,
55 | validate(value) {
56 | if (value.length) {
57 | if (helper.isUsedOnRoutes(value)) {
58 | return "It's already added. Please enter new URL.";
59 | }
60 | return true;
61 | }
62 | return 'It cannot be empty. Please enter it correctly...';
63 | }
64 | }
65 | ];
66 |
67 | inquirer.prompt(questions).then(({ filename, prettyurl = null }) => {
68 | if (prettyurl) {
69 | afterPageCreation(filename, prettyurl);
70 | } else {
71 | process.stdout.write('\n');
72 | process.stdout.write(`New route creation is cancelled...`);
73 | process.stdout.write('\n');
74 | process.exit(0);
75 | }
76 | });
77 | }
78 |
79 | helper.writeRan(askQuestions);
80 |
--------------------------------------------------------------------------------
/helper_scripts/CL_commands/setup.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /* eslint-disable import/no-unresolved */
4 |
5 | const shell = require('shelljs');
6 | const clear = require('cli-clear');
7 | const { exec } = require('child_process');
8 | const path = require('path');
9 | const fs = require('fs');
10 | const helper = require('./__helpers');
11 |
12 | const isCleanSetup = process.env.CLEANSETUP || false;
13 | const projectName = path.basename(path.resolve(helper.config.appDir)) || 'RAN';
14 |
15 | clear();
16 | process.stdin.resume();
17 | process.stdin.setEncoding('utf8');
18 |
19 | function cleanSetup(callback) {
20 | if (!isCleanSetup) return callback();
21 |
22 | const comps = shell
23 | .find(helper.config.componentsDir)
24 | .filter(
25 | (file, index) =>
26 | index !== 0 &&
27 | !file.toLowerCase().includes('appicons') &&
28 | !file.toLowerCase().includes('authfields') &&
29 | !file.toLowerCase().includes('app.js') &&
30 | !file.toLowerCase().includes('theme.js')
31 | );
32 | shell.rm('-rf', comps);
33 |
34 | const pages = shell
35 | .find(helper.config.pagesDir)
36 | .filter(
37 | (file, index) =>
38 | index !== 0 && !file.toLowerCase().includes('_document.js')
39 | );
40 | shell.rm('-rf', pages);
41 |
42 | helper.createPageFromTemplate('index', () => {});
43 | helper.createContainerFromTemplate('Default', () => {});
44 | helper.clearRoutes(() => {
45 | callback();
46 | });
47 | }
48 |
49 | function updateReadme(callback) {
50 | shell.rm('-rf', shell.find(`${helper.config.appDir}README.md`));
51 | helper.updateReadme(projectName, () => {
52 | callback();
53 | });
54 | }
55 |
56 | /**
57 | * Initializes git again
58 | */
59 | function initGit(callback) {
60 | exec('git init && git add . && git commit -m "Initial commit"', callback);
61 | }
62 |
63 | /**
64 | * Deletes a file in the current directory
65 | */
66 | function deleteFileInCurrentDir(file, callback) {
67 | fs.unlink(path.join(__dirname, file), callback);
68 | }
69 |
70 | /**
71 | * Callback function after installing dependencies
72 | */
73 | function installDepsCallback(error) {
74 | process.stdout.write('\n\n');
75 | if (error) {
76 | process.stderr.write(error);
77 | process.stdout.write('\n');
78 | process.exit(1);
79 | }
80 |
81 | deleteFileInCurrentDir('setup.js', () => {
82 | process.stdout.write('Initialising new repository...');
83 | initGit(() => {
84 | cleanSetup(() => {
85 | updateReadme(() => {
86 | clear();
87 | process.stdout.write('\n');
88 | process.stdout.write('\nRAN! is ready to go!');
89 | process.stdout.write('\n');
90 | process.stdout.write('\n');
91 | process.exit(0);
92 | });
93 | });
94 | });
95 | });
96 | }
97 |
98 | /**
99 | * Installs dependencies
100 | */
101 | function installDeps() {
102 | exec('node --version', (err, stdout) => {
103 | const nodeVersion = stdout && parseFloat(stdout.substring(1));
104 | if (nodeVersion < 7 || err) {
105 | installDepsCallback(
106 | err ||
107 | 'Unsupported node.js version, make sure you have the latest version installed.'
108 | );
109 | } else {
110 | installDepsCallback();
111 | }
112 | });
113 | }
114 |
115 | /**
116 | * Deletes the .git folder in dir
117 | */
118 | function cleanRepo(callback) {
119 | shell.rm('-rf', '.git/');
120 | callback();
121 | }
122 |
123 | helper.writeRan(() => {
124 | process.stdout.write('\n');
125 | process.stdout.write('Cleaning RAN! for preparing new project...');
126 | process.stdout.write('\n');
127 |
128 | cleanRepo(() => {
129 | process.stdout.write(
130 | 'Installing dependencies... (This might take a while)'
131 | );
132 | installDeps();
133 | });
134 | });
135 |
--------------------------------------------------------------------------------
/helper_scripts/http.nginx.conf:
--------------------------------------------------------------------------------
1 | #################################################################################
2 | ### Put this file in /etc/nginx/conf.d folder if you want to support HTTP only
3 | ### and make sure you have a line 'include /etc/nginx/conf.d/*.conf;'
4 | ### in your main nginx configuration file
5 | #################################################################################
6 |
7 | #################################################################################
8 | ### HTTP configurations
9 | #################################################################################
10 |
11 | server {
12 | listen 80 default_server;
13 | listen [::]:80 default_server ipv6only=on;
14 |
15 | ### Change "exampleaddress.com" to your host name
16 | server_name localhost exampleaddress.com;
17 |
18 | ### Change "YOUR_DIRECTORY" to your directory
19 | root /var/www/YOUR_DIRECTORY;
20 | index index.html index.htm;
21 |
22 | location / {
23 | ### default port, could be changed if you use next with custom server
24 | proxy_pass http://localhost:3000;
25 |
26 | proxy_http_version 1.1;
27 | proxy_set_header Upgrade $http_upgrade;
28 | proxy_set_header Connection 'upgrade';
29 | proxy_set_header Host $host;
30 | proxy_cache_bypass $http_upgrade;
31 |
32 | ### if you have try_files like this, remove it from our block
33 | ### otherwise next app will not work properly
34 | ### try_files $uri $uri/ =404;
35 | }
36 |
37 | location /sw.js {
38 | add_header Cache-Control "no-cache";
39 | proxy_cache_bypass $http_pragma;
40 | proxy_cache_revalidate on;
41 | expires off;
42 | access_log off;
43 | }
44 |
45 | location /favicon.ico {
46 | log_not_found off;
47 | access_log off;
48 | }
49 |
50 | location /robots.txt {
51 | allow all;
52 | log_not_found off;
53 | access_log off;
54 | }
55 |
56 | location ~ /\. {
57 | deny all;
58 | }
59 |
60 | location ~* /(?:uploads|files)/.*\.js$ {
61 | deny all;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/helper_scripts/https.nginx.conf:
--------------------------------------------------------------------------------
1 | #################################################################################
2 | ### Put this file in /etc/nginx/conf.d folder if you want to support HTTPS
3 | ### and make sure you have a line 'include /etc/nginx/conf.d/*.conf;'
4 | ### in your main nginx configuration file
5 | #################################################################################
6 |
7 | #################################################################################
8 | ### Redirect to the same URL with https://
9 | #################################################################################
10 |
11 | server {
12 | listen 80 default_server;
13 | listen [::]:80 default_server ipv6only=on;
14 |
15 | ### Change "exampleaddress.com" to your host name
16 | server_name localhost exampleaddress.com;
17 |
18 | return 301 https://$server_name$request_uri;
19 | }
20 |
21 | #################################################################################
22 | ### HTTPS configurations
23 | #################################################################################
24 |
25 | server {
26 | listen 443 ssl;
27 |
28 | ### Change "exampleaddress.com" to your host name
29 | server_name localhost exampleaddress.com;
30 |
31 | ### Change "YOUR_DIRECTORY" to your directory
32 | root /var/www/YOUR_DIRECTORY;
33 | index index.html index.htm;
34 |
35 | #################################################################################
36 | ### SSL Config
37 | #################################################################################
38 |
39 | ### Configure the Certificate and Key you got from your CA (e.g. Lets Encrypt)
40 | ssl_certificate /path/to/certificate.crt;
41 | ssl_certificate_key /path/to/server.key;
42 |
43 | ssl_session_timeout 1d;
44 | ssl_session_cache shared:SSL:50m;
45 | ssl_session_tickets off;
46 |
47 | ### Only use TLS v1.2 as Transport Security Protocol
48 | ssl_protocols TLSv1.2;
49 |
50 | ### Only use ciphersuites that are considered modern and secure by Mozilla
51 | ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
52 |
53 | ### Do not let attackers downgrade the ciphersuites in Client Hello
54 | ### Always use server-side offered ciphersuites
55 | ssl_prefer_server_ciphers on;
56 |
57 | ### HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
58 | add_header Strict-Transport-Security max-age=15768000;
59 |
60 | ### Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
61 | ### Uncomment if you want to use your own Diffie-Hellman parameter, which can be generated with: openssl ecparam -genkey -out dhparam.pem -name prime256v1
62 | ### See https://wiki.mozilla.org/Security/Server_Side_TLS#DHE_handshake_and_dhparam
63 | ### ssl_dhparam /path/to/dhparam.pem;
64 |
65 |
66 | ### OCSP Configuration START
67 | #### If you want to provide OCSP Stapling, you can uncomment the following lines
68 | #### See https://www.digitalocean.com/community/tutorials/how-to-configure-ocsp-stapling-on-apache-and-nginx for more infos about OCSP and its use case
69 | #### fetch OCSP records from URL in ssl_certificate and cache them
70 |
71 | ### ssl_stapling on;
72 | ### ssl_stapling_verify on;
73 |
74 | ### verify chain of trust of OCSP response using Root CA and Intermediate certs (you will get this file from your CA)
75 | #ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates;
76 |
77 | #### OCSP Configuration END
78 |
79 | ### To let nginx use its own DNS Resolver
80 | ### resolver ;
81 |
82 | #################################################################################
83 | ### SSL Config - End
84 | #################################################################################
85 |
86 | location / {
87 | ### default port, could be changed if you use next with custom server
88 | proxy_pass http://localhost:3000;
89 |
90 | proxy_http_version 1.1;
91 | proxy_set_header Upgrade $http_upgrade;
92 | proxy_set_header Connection 'upgrade';
93 | proxy_set_header Host $host;
94 | proxy_cache_bypass $http_upgrade;
95 |
96 | ### if you have try_files like this, remove it from our block
97 | ### otherwise next app will not work properly
98 | ### try_files $uri $uri/ =404;
99 | }
100 |
101 | location /sw.js {
102 | add_header Cache-Control "no-cache";
103 | proxy_cache_bypass $http_pragma;
104 | proxy_cache_revalidate on;
105 | expires off;
106 | access_log off;
107 | }
108 |
109 | location /favicon.ico {
110 | log_not_found off;
111 | access_log off;
112 | }
113 |
114 | location /robots.txt {
115 | allow all;
116 | log_not_found off;
117 | access_log off;
118 | }
119 |
120 | location ~ /\. {
121 | deny all;
122 | }
123 |
124 | location ~* /(?:uploads|files)/.*\.js$ {
125 | deny all;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/helper_scripts/prepare-server-on-digitalocean.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | cd ~
4 | curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
5 | sudo apt-get install -y nodejs
6 | sudo apt-get install build-essential
7 |
8 | sudo npm install -g pm2
9 |
10 | # pm2 start npm -- start
11 |
--------------------------------------------------------------------------------
/helper_scripts/run-development.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # this script should not be run directly,
4 | # instead you need to source it from your .bashrc,
5 | # by adding this line:
6 | # . ./run-development.sh
7 | #
8 |
9 | cd ./../
10 | yarn run dev
11 |
--------------------------------------------------------------------------------
/helper_scripts/run-production.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # this script should not be run directly,
4 | # instead you need to source it from your .bashrc,
5 | # by adding this line:
6 | # . ./run-production.sh
7 | #
8 |
9 | cd ./../
10 | yarn run build
11 | pm2 start yarn --name "website" -- start
12 |
--------------------------------------------------------------------------------
/helper_scripts/templates/component.hbs:
--------------------------------------------------------------------------------
1 | {{#if haveStyle}}
2 | import { {{filename}} as Styled{{filename}} } from './styles';
3 | {{/if}}
4 | {{#if haveStore}}
5 | import connect from './store';
6 | {{/if}}
7 |
8 | const {{filename}} = () => (
9 | {{#if haveStyle}}
10 |
11 | {{filename}}
12 |
13 | {{else}}
14 |
15 | {{filename}}
16 |
17 | {{/if}}
18 | );
19 |
20 | {{#if haveStore}}
21 | export default connect({{filename}});
22 | {{else}}
23 | export default {{filename}};
24 | {{/if}}
25 |
--------------------------------------------------------------------------------
/helper_scripts/templates/component_graphql.hbs:
--------------------------------------------------------------------------------
1 | query getPost($postId: ID!) {
2 | Post(id: $postId) {
3 | title
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/helper_scripts/templates/component_store.hbs:
--------------------------------------------------------------------------------
1 | {{#if haveGraphql}}
2 | import { graphql } from 'react-apollo';
3 | import {{graphqlName}}Gql from './{{graphqlName}}.gql';
4 |
5 | const withData = graphql({{graphqlName}}Gql, {
6 | options: ({ postId }) => ({
7 | variables: {
8 | postId
9 | }
10 | }),
11 | props: ({ data: { loading, Post, error } }) => ({
12 | loading,
13 | Post,
14 | error
15 | })
16 | });
17 |
18 | export default comp => withData(comp);
19 | {{else}}
20 | import { connect } from 'react-redux';
21 |
22 | const mapStateToProps = () => ({
23 | example: true
24 | });
25 |
26 | const mapDispatchToProps = dispatch => ({
27 | actions: {
28 | exampleCall: () => dispatch({ type: 'EXAMPLE' })
29 | }
30 | });
31 |
32 | export default comp => connect(mapStateToProps, mapDispatchToProps)(comp);
33 | {{/if}}
34 |
--------------------------------------------------------------------------------
/helper_scripts/templates/component_style.hbs:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | // eslint-disable-next-line import/prefer-default-export
4 | export const {{filename}} = styled.div`
5 | border: 1px solid red;
6 | padding: 5px;
7 |
8 | > span {
9 | color: red;
10 | font-weight: bold;
11 | }
12 | `;
13 |
--------------------------------------------------------------------------------
/helper_scripts/templates/container.hbs:
--------------------------------------------------------------------------------
1 | import { Helmet } from 'react-helmet';
2 | import PropTypes from 'prop-types';
3 | import App from '../components/App';
4 |
5 | const Default = props => (
6 |
7 |
8 |
9 | {props.title !== '' ? props.title : ''}
10 |
11 |
12 | {props.children}
13 |
14 | );
15 |
16 | Default.propTypes = {
17 | title: PropTypes.string,
18 | url: PropTypes.object.isRequired,
19 | children: PropTypes.element.isRequired
20 | };
21 |
22 | Default.defaultProps = {
23 | title: ''
24 | };
25 |
26 | export default Default;
27 |
--------------------------------------------------------------------------------
/helper_scripts/templates/page.hbs:
--------------------------------------------------------------------------------
1 | import { Helmet } from 'react-helmet';
2 | import App from '../components/App';
3 | import withData from '../libraries/withData';
4 | import { dump } from '../libraries/helpers';
5 |
6 | export default withData(props =>
7 |
8 |
9 | {{filename}}
10 |
11 |
12 |
{{filename}}
13 |
HELLO WORLD! HELLO FROM RAN!
14 |
15 |
{dump(props)}
16 |
17 |
18 | );
19 |
--------------------------------------------------------------------------------
/helper_scripts/templates/readme.hbs:
--------------------------------------------------------------------------------
1 | # {{projectname}}
2 |
3 | ## Commands
4 |
5 | Best feature of RAN! is **CL commands**. You can just run one command to create page with route! [Click here](docs/Commands.md) to see details how It works on RAN!.
6 |
7 | ## Documentation
8 |
9 | [Click here](https://www.rantoolkit.com/) to see all details of RAN!
10 |
11 | ## Links
12 |
13 | [RAN @ Github](https://github.com/Sly777/ran)
14 | [RAN @ Gitter](https://gitter.im/ran-boilerplate/Lobby)
15 | [RAN License](https://github.com/Sly777/ran/blob/master/LICENSE.md)
16 | [RAN Code of Conduct](https://github.com/Sly777/ran/blob/master/CODE_OF_CONDUCT.md)
17 |
18 | Presented by;
19 | 
20 | ### RAN : React . GraphQL . Next.js Toolkit
21 |
--------------------------------------------------------------------------------
/helper_scripts/templates/reducer.hbs:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | export default function getReducer(client) {
4 | return combineReducers({
5 | apollo: client.reducer()
6 | });
7 | }
8 |
--------------------------------------------------------------------------------
/helper_scripts/templates/route.hbs:
--------------------------------------------------------------------------------
1 | routes.add('{{filename}}', '/{{prettyurl}}');
2 |
--------------------------------------------------------------------------------
/libraries/apolloClient.js:
--------------------------------------------------------------------------------
1 | import { ApolloClient } from 'apollo-client';
2 | import { createHttpLink } from 'apollo-link-http';
3 | import { InMemoryCache } from 'apollo-cache-inmemory';
4 | import { ApolloLink } from 'apollo-client-preset';
5 | import persist from './persist';
6 |
7 | let apolloClient = null;
8 |
9 | const httpLink = createHttpLink({
10 | uri: 'https://api.graph.cool/simple/v1/cj7ke77fv0e9i0122pflagbvx',
11 | credentials: 'include'
12 | });
13 |
14 | function createClient(headers, token, initialState) {
15 | let accessToken = token;
16 |
17 | (async () => {
18 | // eslint-disable-next-line no-param-reassign
19 | accessToken = token || (await persist.willGetAccessToken());
20 | })();
21 |
22 | const authLink = new ApolloLink((operation, forward) => {
23 | operation.setContext({
24 | headers: {
25 | authorization: accessToken
26 | }
27 | });
28 | return forward(operation);
29 | }).concat(httpLink);
30 |
31 | return new ApolloClient({
32 | headers,
33 | link: authLink,
34 | connectToDevTools: process.browser,
35 | ssrMode: !process.browser,
36 | cache: new InMemoryCache().restore(initialState || {})
37 | });
38 | }
39 |
40 | export default (headers, token, initialState) => {
41 | if (!process.browser) {
42 | return createClient(headers, token, initialState);
43 | }
44 | if (!apolloClient) {
45 | apolloClient = createClient(headers, token, initialState);
46 | }
47 | return apolloClient;
48 | };
49 |
--------------------------------------------------------------------------------
/libraries/commonTypes.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/libraries/commonTypes.js
--------------------------------------------------------------------------------
/libraries/helpers.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-restricted-syntax */
2 |
3 | export default () => {};
4 |
5 | export function dump(obj) {
6 | let out = '';
7 | for (const key in obj) {
8 | if (Object.prototype.hasOwnProperty.call(obj, key)) {
9 | out += `${key}: ${obj[key]}\n`;
10 | }
11 | }
12 |
13 | return out;
14 | }
15 |
16 | export function mergeObjects(...args) {
17 | const dst = {};
18 | let src;
19 | let p;
20 | const aargs = [].splice.call(args, 0);
21 |
22 | while (aargs.length > 0) {
23 | const idx = 0;
24 | src = aargs.splice(0, 1)[idx];
25 | if (toString.call(src) === '[object Object]') {
26 | for (p in src) {
27 | if (Object.prototype.hasOwnProperty.call(src, p)) {
28 | if (toString.call(src[p]) === '[object Object]') {
29 | dst[p] = mergeObjects(dst[p] || {}, src[p]);
30 | } else {
31 | dst[p] = src[p];
32 | }
33 | }
34 | }
35 | }
36 | }
37 |
38 | return dst;
39 | }
40 |
41 | /* eslint-enable no-restricted-syntax */
42 |
--------------------------------------------------------------------------------
/libraries/middleware.js:
--------------------------------------------------------------------------------
1 | import { applyMiddleware, compose } from 'redux';
2 |
3 | export default function createMiddleware(clientMiddleware) {
4 | const middleware = applyMiddleware(clientMiddleware);
5 | const composeEnhancers =
6 | typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
7 | ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
8 | // Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
9 | })
10 | : compose;
11 |
12 | return composeEnhancers(middleware);
13 | }
14 |
--------------------------------------------------------------------------------
/libraries/persist.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import cookies from 'js-cookie';
3 |
4 | export default class persist {
5 | static get ACCESS_TOKEN_KEY(): string {
6 | return 'accessToken';
7 | }
8 |
9 | static async willGetAccessToken() {
10 | return cookies.get(persist.ACCESS_TOKEN_KEY);
11 | }
12 |
13 | static async willSetAccessToken(value: string) {
14 | return cookies.set(persist.ACCESS_TOKEN_KEY, value);
15 | }
16 |
17 | static async willRemoveAccessToken() {
18 | return cookies.remove(persist.ACCESS_TOKEN_KEY);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/libraries/redirect.js:
--------------------------------------------------------------------------------
1 | import Router from 'next/router';
2 |
3 | export default (context, target) => {
4 | if (context.res) {
5 | // server
6 | // 303: "See other"
7 | context.res.writeHead(303, { Location: target });
8 | context.res.end();
9 | } else {
10 | // In the browser, we just pretend like this never even happened ;)
11 | Router.replace(target);
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/libraries/reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { reducer as authReducer } from '../components/AuthFields/store';
3 |
4 | export default function getReducer() {
5 | return combineReducers({
6 | auth: authReducer
7 | });
8 | }
9 |
--------------------------------------------------------------------------------
/libraries/reduxStore.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux';
2 | import thunkMiddleware from 'redux-thunk';
3 |
4 | import { dispatchers } from '../components/AuthFields/store';
5 | import rootReducer from './reducer';
6 | import createMiddleware from './middleware';
7 | import persist from './persist';
8 |
9 | let reduxStore = null;
10 | const middleware = createMiddleware(thunkMiddleware);
11 |
12 | export default (initialState, token) => {
13 | let store;
14 | if (!process.browser || !reduxStore) {
15 | store = createStore(rootReducer(), initialState, middleware);
16 |
17 | let tokenInStore = store.getState().auth.token;
18 |
19 | if (!tokenInStore) {
20 | (async () => {
21 | tokenInStore =
22 | token || (await Promise.resolve(persist.willGetAccessToken()));
23 |
24 | if (typeof token === 'string' && !token.includes('Error')) {
25 | if (token.length) {
26 | store.dispatch(dispatchers.signIn(token));
27 | } else {
28 | store.dispatch(dispatchers.signOut());
29 | }
30 | } else {
31 | store.dispatch(dispatchers.signOut());
32 | }
33 | })();
34 | }
35 |
36 | if (!process.browser) {
37 | return store;
38 | }
39 |
40 | reduxStore = store;
41 | }
42 | return reduxStore;
43 | };
44 |
--------------------------------------------------------------------------------
/libraries/theme.js:
--------------------------------------------------------------------------------
1 | import { mergeObjects } from './helpers';
2 |
3 | const themeList = {};
4 |
5 | themeList.extend = (themename, newsetting) =>
6 | mergeObjects(themeList[themename], newsetting);
7 |
8 | themeList.main = {
9 | font: {
10 | sizes: {
11 | normal: '14px',
12 | big: '15px',
13 | bigger: '16px',
14 | small: '13px',
15 | smaller: '12px',
16 | tiny: '11px'
17 | },
18 | family: {
19 | normal:
20 | 'Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace, serif'
21 | }
22 | },
23 | spacing: {
24 | normal: '10px',
25 | big: '15px',
26 | bigger: '20px',
27 | huge: '40px',
28 | small: '10px',
29 | smaller: '5px',
30 | noSpace: '0'
31 | },
32 | alignment: {
33 | horizontalCenter: '0 auto',
34 | center: 'auto'
35 | },
36 | colors: {
37 | main: '#22BAD9',
38 | success: '#5cb85c',
39 | warn: '#ffc067',
40 | error: '#d9534f',
41 | background: '#ffffff',
42 | text: '#000000',
43 | textAlt: '#ffffff'
44 | }
45 | };
46 |
47 | themeList.inverted = themeList.extend('main', {
48 | colors: {
49 | background: '#000000',
50 | text: '#ffffff'
51 | }
52 | });
53 |
54 | themeList.eightbit = themeList.extend('inverted', {
55 | colors: {
56 | main: '#40337f',
57 | success: '#1bcb01',
58 | error: '#722640',
59 | background: '#000000',
60 | text: '#ffffff'
61 | },
62 | font: {
63 | family: {
64 | normal: 'Consolas, monaco, monospace'
65 | }
66 | }
67 | });
68 |
69 | export default themeList;
70 |
--------------------------------------------------------------------------------
/libraries/validations.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-useless-escape */
2 | // @flow
3 |
4 | export default () => {};
5 |
6 | export function isStringEmpty(text?: string) {
7 | return !text || text === '' || text.trim() === '';
8 | }
9 |
10 | export function isEmail(email: string) {
11 | const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
12 | return re.test(email);
13 | }
14 |
15 | /* eslint-enable no-useless-escape */
16 |
--------------------------------------------------------------------------------
/libraries/withData.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import { ApolloProvider, getDataFromTree } from 'react-apollo';
4 | import { Provider as ReduxProvider } from 'react-redux';
5 | import PropTypes from 'prop-types';
6 | import 'isomorphic-fetch';
7 | import cookies from 'next-cookies';
8 | import apolloClient from './apolloClient';
9 | import reduxStore from './reduxStore';
10 | import persist from './persist';
11 |
12 | type Props = {
13 | headers: HeadersType,
14 | accessToken?: string,
15 | router: Object,
16 | apolloState: Object,
17 | reduxState: Object
18 | };
19 |
20 | type Context = {
21 | pathname: string,
22 | query: Object,
23 | asPath: string,
24 | req?: {
25 | headers?: Object
26 | },
27 | res?: Object,
28 | jsonPageRes?: Object,
29 | err?: Object
30 | };
31 |
32 | export default (
33 | Component: React.ComponentType<*>
34 | ): React.ComponentType =>
35 | class extends React.Component {
36 | static propTypes = {
37 | apolloState: PropTypes.object.isRequired,
38 | reduxState: PropTypes.object.isRequired,
39 | headers: PropTypes.object.isRequired,
40 | accessToken: PropTypes.string
41 | };
42 |
43 | static defaultProps = {
44 | accessToken: ''
45 | };
46 |
47 | constructor(props: Props) {
48 | super(props);
49 | this.apolloClient = apolloClient({}, '', this.props.apolloState);
50 | this.reduxStore = reduxStore(this.props.reduxState);
51 | }
52 |
53 | static async getInitialProps(ctx: Context) {
54 | let apolloState = {};
55 | let serverState = {};
56 |
57 | const headers = ctx.req ? ctx.req.headers : {};
58 | const token: string = cookies(ctx)[persist.ACCESS_TOKEN_KEY];
59 |
60 | const props = {
61 | router: {
62 | url: { query: ctx.query, pathname: ctx.pathname }
63 | },
64 | ...(await (typeof Component.getInitialProps === 'function'
65 | ? Component.getInitialProps(ctx)
66 | : {}))
67 | };
68 |
69 | if (!process.browser) {
70 | const client = apolloClient(headers || {}, token || '', {}, ctx);
71 | const store = reduxStore();
72 |
73 | try {
74 | const app = (
75 |
76 |
77 |
78 |
79 |
80 | );
81 | await getDataFromTree(app);
82 | } catch (error) {
83 | // Prevent Apollo Client GraphQL errors from crashing SSR.
84 | // Handle them in components via the data.error prop:
85 | // https://github.com/apollographql/react-apollo/issues/406
86 | // http://dev.apollodata.com/react/api-queries.html#graphql-query-data-error
87 | }
88 |
89 | apolloState = client.cache.extract();
90 | serverState = store.getState();
91 | }
92 |
93 | return {
94 | reduxState: serverState,
95 | apolloState,
96 | headers,
97 | ...props
98 | };
99 | }
100 |
101 | apolloClient: Object;
102 |
103 | reduxStore: Object;
104 |
105 | render() {
106 | return (
107 |
108 |
109 |
110 |
111 |
112 | );
113 | }
114 | };
115 |
--------------------------------------------------------------------------------
/lighthouse_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "lighthouse:default",
3 | "settings": {
4 | "onlyCategories": ["performance"]
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 | const { IgnorePlugin } = require('webpack');
3 | const OfflinePlugin = require('offline-plugin');
4 | const Dotenv = require('dotenv-webpack');
5 | const router = require('./routes');
6 |
7 | const initExport = {
8 | webpack: (config, { dev, isServer }) => {
9 | const prod = !dev;
10 |
11 | config.plugins.push(new Dotenv({ path: './public.env' }));
12 | config.plugins.push(new IgnorePlugin(/^\.\/locale$/, /moment$/));
13 |
14 | if (dev) {
15 | config.module.rules.push({
16 | test: /\.(jsx?|gql|graphql)$/,
17 | loader: 'eslint-loader',
18 | exclude: ['/node_modules/', '/.next/', '/helper_scripts/'],
19 | enforce: 'pre'
20 | });
21 | }
22 |
23 | if (process.env.ANALYZE_BUILD) {
24 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
25 | config.plugins.push(
26 | new BundleAnalyzerPlugin({
27 | analyzerMode: 'server',
28 | analyzerPort: isServer ? 8888 : 8889,
29 | openAnalyzer: true
30 | })
31 | );
32 | }
33 |
34 | if (prod && process.env.OFFLINE_SUPPORT) {
35 | config.plugins.push(
36 | new OfflinePlugin({
37 | publicPath: '/',
38 | relativePaths: false,
39 | externals: ['/', '/manifest.html'],
40 | excludes: ['.htaccess'],
41 | safeToUseOptionalCaches: true,
42 | caches: 'all',
43 | rewrites: function rewrites(asset) {
44 | if (
45 | asset.indexOf('.hot-update.js') > -1 ||
46 | asset.indexOf('build-stats.json') > -1 ||
47 | asset === 'BUILD_ID' ||
48 | asset.indexOf('dist/') === 0
49 | ) {
50 | return null;
51 | }
52 |
53 | if (asset[0] === '/') {
54 | return asset;
55 | }
56 |
57 | if (asset.indexOf('bundles/pages/') === 0) {
58 | return `/_next/-/${asset
59 | .replace('bundles/pages', 'page')
60 | .replace('index.js', '')
61 | .replace(/\.js$/, '')}`;
62 | }
63 |
64 | return `/_next/-/${asset}`;
65 | },
66 | autoUpdate: 1000 * 60 * 5,
67 | __tests: dev ? { ignoreRuntime: true } : {},
68 | ServiceWorker: {
69 | events: true,
70 | navigateFallbackURL: '/'
71 | },
72 | AppCache: {
73 | directory: './',
74 | events: true
75 | }
76 | })
77 | );
78 | }
79 |
80 | return config;
81 | }
82 | };
83 |
84 | if (process.env.STATIC_EXPORT) {
85 | initExport.exportPathMap = function exportPathMap() {
86 | const routes = {};
87 | routes['/'] = {
88 | page: '/'
89 | };
90 | router.routes.forEach(route => {
91 | if (!route.pattern.includes(':')) {
92 | routes[route.pattern] = {
93 | page: route.page
94 | };
95 | }
96 | });
97 |
98 | return routes;
99 | };
100 | }
101 |
102 | /* eslint-enable global-require */
103 | module.exports = initExport;
104 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ran-boilerplate",
3 | "version": "0.9.0",
4 | "main": "server.js",
5 | "description": "React . Apollo (GraphQL) . Next.js Toolkit",
6 | "author": "Ilker Guller (http://ilkerguller.com)",
7 | "license": "MIT",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/Sly777/ran"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/Sly777/ran/issues"
14 | },
15 | "keywords": [
16 | "next.js",
17 | "react",
18 | "graphql",
19 | "boilerplate",
20 | "apollo",
21 | "styled-components",
22 | "ran",
23 | "run",
24 | "toolkit"
25 | ],
26 | "pre-commit": "lint-staged",
27 | "lint-staged": {
28 | "*.js": [
29 | "eslint --fix",
30 | "git add"
31 | ]
32 | },
33 | "scripts": {
34 | "start": "cross-env NODE_ENV=production node server.js",
35 | "start:multicore": "cross-env NODE_ENV=production pm2 start server.js -i max --attach",
36 | "build": "npm run clean-cache && next build",
37 | "clean-cache": "rimraf -rf ./node_modules/.cache",
38 | "dev": "npm run lint && npm run clean-cache && node server.js",
39 | "lint": "eslint --fix --ext .js pages components containers libraries server helper_scripts",
40 | "lint:watch": "esw -w --fix components libraries server",
41 | "analyze": "cross-env ANALYZE_BUILD=true npm run build",
42 | "setup": "node ./helper_scripts/CL_commands/setup.js && npm run setup:after",
43 | "setup:clean": "cross-env CLEANSETUP=true node ./helper_scripts/CL_commands/setup.js && npm run setup:after",
44 | "setup:after": "npm run build",
45 | "create:page": "node ./helper_scripts/CL_commands/create_page.js",
46 | "create:route": "node ./helper_scripts/CL_commands/create_route.js",
47 | "create:container": "node ./helper_scripts/CL_commands/create_container.js",
48 | "create:component": "node ./helper_scripts/CL_commands/create_component.js",
49 | "lint-staged": "lint-staged",
50 | "heroku-postbuild": "npm run build",
51 | "prestart": "npm run build",
52 | "release": "release",
53 | "graphql:play": "graphql playground",
54 | "graphql:update_schema": "graphql get-schema",
55 | "graphql:see_graph": "graphql voyager",
56 | "build:static_export": "cross-env STATIC_EXPORT=true npm run build && cross-env STATIC_EXPORT=true next export",
57 | "zip-for-beanstalk": "npm prune --production && zip -qr ran-boilerplate.zip . -x \"*.zip\""
58 | },
59 | "dependencies": {
60 | "apollo-boost": "0.1.23",
61 | "apollo-client-preset": "1.0.8",
62 | "babel-plugin-import-graphql": "2.6.2",
63 | "babel-plugin-styled-components": "1.10.0",
64 | "chalk": "2.4.1",
65 | "color": "3.1.0",
66 | "compression": "1.7.3",
67 | "cors": "2.8.5",
68 | "cross-env": "5.2.0",
69 | "dotenv": "6.2.0",
70 | "dotenv-webpack": "1.6.0",
71 | "express": "4.16.4",
72 | "graphql": "^14.0.2",
73 | "helmet": "3.15.0",
74 | "ip": "1.1.5",
75 | "isomorphic-fetch": "2.2.1",
76 | "js-cookie": "2.2.0",
77 | "lru-cache": "5.1.1",
78 | "moment": "2.23.0",
79 | "next": "7.0.2",
80 | "next-cookies": "Sly777/next-cookies#master",
81 | "next-routes": "1.4.2",
82 | "offline-plugin": "5.0.6",
83 | "prop-types": "15.6.2",
84 | "react": "16.7.0",
85 | "react-apollo": "2.3.3",
86 | "react-dom": "16.7.0",
87 | "react-helmet": "5.2.0",
88 | "react-redux": "6.0.0",
89 | "redux": "4.0.1",
90 | "rimraf": "2.6.3",
91 | "styled-components": "4.1.3"
92 | },
93 | "devDependencies": {
94 | "@babel/plugin-transform-flow-strip-types": "7.2.3",
95 | "babel-eslint": "10.0.1",
96 | "cli-clear": "1.0.4",
97 | "eslint": "5.11.1",
98 | "eslint-config-airbnb": "17.1.0",
99 | "eslint-config-prettier": "3.3.0",
100 | "eslint-loader": "2.1.1",
101 | "eslint-plugin-flowtype": "3.2.0",
102 | "eslint-plugin-graphql": "3.0.1",
103 | "eslint-plugin-import": "2.14.0",
104 | "eslint-plugin-jsx-a11y": "6.1.2",
105 | "eslint-plugin-prettier": "3.0.1",
106 | "eslint-plugin-react": "7.12.2",
107 | "eslint-watch": "4.0.2",
108 | "figlet": "1.2.1",
109 | "flow-bin": "0.89.0",
110 | "graphql-cli": "3.0.4",
111 | "graphql-cli-voyager": "0.1.3",
112 | "handlebars": "4.0.12",
113 | "husky": "1.3.1",
114 | "inquirer": "6.2.1",
115 | "lint-staged": "8.1.0",
116 | "ngrok": "3.1.0",
117 | "pre-commit": "1.2.2",
118 | "prettier": "1.15.3",
119 | "replace-in-file": "3.4.2",
120 | "shelljs": "0.8.3",
121 | "webpack-bundle-analyzer": "3.0.3"
122 | },
123 | "collective": {
124 | "type": "opencollective",
125 | "url": "https://opencollective.com/ran"
126 | },
127 | "husky": {
128 | "hooks": {
129 | "pre-commit": "lint-staged"
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Document, { Head, Main, NextScript } from 'next/document';
2 | import Helmet from 'react-helmet';
3 | import { ServerStyleSheet } from 'styled-components';
4 | import AppIcons from '../components/AppIcons';
5 |
6 | export default class MyDocument extends Document {
7 | static async getInitialProps({ renderPage }) {
8 | const sheet = new ServerStyleSheet();
9 | const page = renderPage(App => props =>
10 | sheet.collectStyles( )
11 | );
12 | const styleTags = sheet.getStyleElement();
13 |
14 | // see https://github.com/nfl/react-helmet#server-usage for more information
15 | // 'head' was occupied by 'renderPage().head', we cannot use it
16 | return { ...page, styleTags, helmet: Helmet.rewind() };
17 | }
18 |
19 | // should render on
20 | helmetHtmlAttrComponents() {
21 | return this.props.helmet.htmlAttributes.toComponent();
22 | }
23 |
24 | // should render on
25 | helmetHeadComponents() {
26 | const keys = Object.keys(this.props.helmet)
27 | .filter(el => el !== 'htmlAttributes')
28 | .map(el => this.props.helmet[el].toComponent())
29 | .filter(
30 | el =>
31 | el.length > 0 ||
32 | !(Object.keys(el).length === 0 && el.constructor === Object)
33 | );
34 |
35 | return keys.length ? keys : [];
36 | }
37 |
38 | render() {
39 | return (
40 |
41 |
42 |
43 |
44 |
45 | {this.helmetHeadComponents()}
46 | {AppIcons()}
47 | {this.props.styleTags}
48 |
49 |
50 |
51 |
52 |
53 |
54 | );
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/pages/create.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import CreatePost from '../components/CreatePost';
4 | import withData from '../libraries/withData';
5 | import DefaultCon from '../containers/Default';
6 |
7 | export default withData(props => (
8 |
9 |
10 |
11 | ));
12 |
--------------------------------------------------------------------------------
/pages/details.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import { withRouter } from 'next/router';
4 | import PostInfo from '../components/PostInfo';
5 | import withData from '../libraries/withData';
6 | import DefaultCon from '../containers/Default';
7 |
8 | export default withData(
9 | withRouter(props => (
10 |
14 |
18 |
19 | ))
20 | );
21 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import PostList from '../components/PostList';
4 | import withData from '../libraries/withData';
5 | import DefaultCon from '../containers/Default';
6 |
7 | export default withData(props => (
8 |
9 |
10 |
11 | ));
12 |
--------------------------------------------------------------------------------
/pages/signin.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import SignInForm from '../components/SignInForm';
4 | import withData from '../libraries/withData';
5 | import DefaultCon from '../containers/Default';
6 |
7 | export default withData(props => (
8 |
9 |
10 |
11 | ));
12 |
--------------------------------------------------------------------------------
/pages/signup.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import SignUpForm from '../components/SignUpForm';
4 | import withData from '../libraries/withData';
5 | import DefaultCon from '../containers/Default';
6 |
7 | export default withData(props => (
8 |
9 |
10 |
11 | ));
12 |
--------------------------------------------------------------------------------
/routes.js:
--------------------------------------------------------------------------------
1 | const routes = require('next-routes')();
2 |
3 | //
4 | // Because of awesome Next.js, You don't need to add routes for all pages.
5 | // Every file on Pages folder basically has route as they named.
6 | // (index.js => /, about.js => /about, ...etc.)
7 | //
8 | // If you want to change url (for SEO or put different path), please add your route below.
9 | // for more info, please look at https://github.com/Sly777/ran/blob/master/docs/Routing.md
10 | //
11 | //
12 | // Please add your route between of comments
13 | //
14 | // ------------ ROUTES ---------------
15 | // @RANStartRoutes
16 | routes.add('details', '/details/:postId/:postTitle');
17 | routes.add('create', '/create_post');
18 | routes.add('signin', '/sign_in');
19 | routes.add('signup', '/sign_up');
20 | // @RANEndRoutes
21 | // ------------ ROUTES ---------------
22 | //
23 | //
24 |
25 | module.exports = routes;
26 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | const express = require('express');
3 | const next = require('next');
4 | const compression = require('compression');
5 | const LRUCache = require('lru-cache');
6 | const path = require('path');
7 | const fs = require('fs');
8 | const cors = require('cors');
9 | const helmet = require('helmet');
10 | const dotenv = require('dotenv');
11 |
12 | dotenv.config();
13 |
14 | const isDev = process.env.NODE_ENV !== 'production';
15 | const isProd = !isDev;
16 | const ngrok = isDev && process.env.ENABLE_TUNNEL ? require('ngrok') : null;
17 | const router = require('./routes');
18 | const logger = require('./server/logger');
19 |
20 | const customHost = process.env.HOST;
21 | const host = customHost || null;
22 | const prettyHost = customHost || 'localhost';
23 | const port = parseInt(process.env.PORT, 10) || 3000;
24 | const publicEnvFilename = 'public.env';
25 |
26 | const app = next({ dev: isDev });
27 | const handle = app.getRequestHandler();
28 |
29 | const ssrCache = new LRUCache({
30 | max: 100,
31 | maxAge: 1000 * 60 * 60 // 1hour
32 | });
33 |
34 | // share public env variables (if not already set)
35 | try {
36 | if (fs.existsSync(path.resolve(__dirname, publicEnvFilename))) {
37 | const publicEnv = dotenv.parse(
38 | fs.readFileSync(path.resolve(__dirname, publicEnvFilename))
39 | );
40 | Object.keys(publicEnv).forEach(key => {
41 | if (!process.env[key]) {
42 | process.env[key] = publicEnv[key];
43 | }
44 | });
45 | }
46 | } catch (err) {
47 | // silence is golden
48 | }
49 |
50 | const buildId = isProd
51 | ? fs.readFileSync('./.next/BUILD_ID', 'utf8').toString()
52 | : null;
53 |
54 | /*
55 | * NB: make sure to modify this to take into account anything that should trigger
56 | * an immediate page change (e.g a locale stored in req.session)
57 | */
58 | const getCacheKey = function getCacheKey(req) {
59 | return `${req.url}`;
60 | };
61 |
62 | const renderAndCache = function renderAndCache(
63 | req,
64 | res,
65 | pagePath,
66 | queryParams
67 | ) {
68 | const key = getCacheKey(req);
69 |
70 | if (ssrCache.has(key) && !isDev) {
71 | console.log(`CACHE HIT: ${key}`);
72 | res.send(ssrCache.get(key));
73 | return;
74 | }
75 |
76 | app
77 | .renderToHTML(req, res, pagePath, queryParams)
78 | .then(html => {
79 | // Let's cache this page
80 | if (!isDev) {
81 | console.log(`CACHE MISS: ${key}`);
82 | ssrCache.set(key, html);
83 | }
84 |
85 | res.send(html);
86 | })
87 | .catch(err => {
88 | app.renderError(err, req, res, pagePath, queryParams);
89 | });
90 | };
91 |
92 | const routerHandler = router.getRequestHandler(
93 | app,
94 | ({ req, res, route, query }) => {
95 | renderAndCache(req, res, route.page, query);
96 | }
97 | );
98 |
99 | app.prepare().then(() => {
100 | const server = express();
101 |
102 | server.use(compression({ threshold: 0 }));
103 | server.use(
104 | cors({
105 | origin:
106 | prettyHost.indexOf('http') !== -1 ? prettyHost : `http://${prettyHost}`,
107 | credentials: true
108 | })
109 | );
110 | server.use(helmet());
111 | server.use(routerHandler);
112 |
113 | server.get(`/favicon.ico`, (req, res) =>
114 | app.serveStatic(req, res, path.resolve('./static/icons/favicon.ico'))
115 | );
116 |
117 | server.get('/sw.js', (req, res) =>
118 | app.serveStatic(req, res, path.resolve('./.next/sw.js'))
119 | );
120 |
121 | server.get('/manifest.html', (req, res) =>
122 | app.serveStatic(req, res, path.resolve('./.next/manifest.html'))
123 | );
124 |
125 | server.get('/manifest.appcache', (req, res) =>
126 | app.serveStatic(req, res, path.resolve('./.next/manifest.appcache'))
127 | );
128 |
129 | if (isProd) {
130 | server.get('/_next/-/app.js', (req, res) =>
131 | app.serveStatic(req, res, path.resolve('./.next/app.js'))
132 | );
133 |
134 | const hash = buildId;
135 |
136 | server.get(`/_next/${hash}/app.js`, (req, res) =>
137 | app.serveStatic(req, res, path.resolve('./.next/app.js'))
138 | );
139 | }
140 |
141 | server.get('*', (req, res) => handle(req, res));
142 |
143 | server.listen(port, host, err => {
144 | if (err) {
145 | return logger.error(err.message);
146 | }
147 |
148 | if (ngrok) {
149 | ngrok.connect(
150 | port,
151 | (innerErr, url) => {
152 | if (innerErr) {
153 | return logger.error(innerErr);
154 | }
155 | logger.appStarted(port, prettyHost, url);
156 | }
157 | );
158 | } else {
159 | logger.appStarted(port, prettyHost);
160 | }
161 | });
162 | });
163 |
--------------------------------------------------------------------------------
/server/logger.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | const chalk = require('chalk');
4 | const ip = require('ip');
5 |
6 | const divider = chalk.gray('\n-----------------------------------');
7 |
8 | /**
9 | * Logger middleware, you can customize it to make messages more personal
10 | */
11 | const logger = {
12 | // Called whenever there's an error on the server we want to print
13 | error: err => {
14 | console.error(chalk.red(err));
15 | },
16 |
17 | // Called when express.js app starts on given port w/o errors
18 | appStarted: (port, host, tunnelStarted) => {
19 | console.log(`Server started ! ${chalk.green('✓')}`);
20 |
21 | // If the tunnel started, log that and the URL it's available at
22 | if (tunnelStarted) {
23 | console.log(`Tunnel initialised ${chalk.green('✓')}`);
24 | }
25 |
26 | console.log(`
27 | ${chalk.bold('Access URLs:')}${divider}
28 | Localhost: ${chalk.magenta(`http://${host}:${port}`)}
29 | LAN: ${chalk.magenta(`http://${ip.address()}:${port}`) +
30 | (tunnelStarted
31 | ? `\n Proxy: ${chalk.magenta(tunnelStarted)}`
32 | : '')}${divider}
33 | ${chalk.blue(`Press ${chalk.italic('CTRL-C')} to stop`)}
34 | `);
35 | }
36 | };
37 |
38 | module.exports = logger;
39 |
--------------------------------------------------------------------------------
/static/icons/android-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/android-icon-144x144.png
--------------------------------------------------------------------------------
/static/icons/android-icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/android-icon-192x192.png
--------------------------------------------------------------------------------
/static/icons/android-icon-36x36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/android-icon-36x36.png
--------------------------------------------------------------------------------
/static/icons/android-icon-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/android-icon-48x48.png
--------------------------------------------------------------------------------
/static/icons/android-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/android-icon-72x72.png
--------------------------------------------------------------------------------
/static/icons/android-icon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/android-icon-96x96.png
--------------------------------------------------------------------------------
/static/icons/apple-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/apple-icon-114x114.png
--------------------------------------------------------------------------------
/static/icons/apple-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/apple-icon-120x120.png
--------------------------------------------------------------------------------
/static/icons/apple-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/apple-icon-144x144.png
--------------------------------------------------------------------------------
/static/icons/apple-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/apple-icon-152x152.png
--------------------------------------------------------------------------------
/static/icons/apple-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/apple-icon-180x180.png
--------------------------------------------------------------------------------
/static/icons/apple-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/apple-icon-57x57.png
--------------------------------------------------------------------------------
/static/icons/apple-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/apple-icon-60x60.png
--------------------------------------------------------------------------------
/static/icons/apple-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/apple-icon-72x72.png
--------------------------------------------------------------------------------
/static/icons/apple-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/apple-icon-76x76.png
--------------------------------------------------------------------------------
/static/icons/apple-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/apple-icon-precomposed.png
--------------------------------------------------------------------------------
/static/icons/apple-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/apple-icon.png
--------------------------------------------------------------------------------
/static/icons/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 | #ffffff
--------------------------------------------------------------------------------
/static/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/static/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/static/icons/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/favicon-96x96.png
--------------------------------------------------------------------------------
/static/icons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/favicon.ico
--------------------------------------------------------------------------------
/static/icons/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "App",
3 | "icons": [
4 | {
5 | "src": "\/android-icon-36x36.png",
6 | "sizes": "36x36",
7 | "type": "image\/png",
8 | "density": "0.75"
9 | },
10 | {
11 | "src": "\/android-icon-48x48.png",
12 | "sizes": "48x48",
13 | "type": "image\/png",
14 | "density": "1.0"
15 | },
16 | {
17 | "src": "\/android-icon-72x72.png",
18 | "sizes": "72x72",
19 | "type": "image\/png",
20 | "density": "1.5"
21 | },
22 | {
23 | "src": "\/android-icon-96x96.png",
24 | "sizes": "96x96",
25 | "type": "image\/png",
26 | "density": "2.0"
27 | },
28 | {
29 | "src": "\/android-icon-144x144.png",
30 | "sizes": "144x144",
31 | "type": "image\/png",
32 | "density": "3.0"
33 | },
34 | {
35 | "src": "\/android-icon-192x192.png",
36 | "sizes": "192x192",
37 | "type": "image\/png",
38 | "density": "4.0"
39 | }
40 | ]
41 | }
--------------------------------------------------------------------------------
/static/icons/ms-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/ms-icon-144x144.png
--------------------------------------------------------------------------------
/static/icons/ms-icon-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/ms-icon-150x150.png
--------------------------------------------------------------------------------
/static/icons/ms-icon-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/ms-icon-310x310.png
--------------------------------------------------------------------------------
/static/icons/ms-icon-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sly777/ran/9879d908d2a8f79b4cd1d23910db62960a99fef8/static/icons/ms-icon-70x70.png
--------------------------------------------------------------------------------
/types/commonTypes.js.flow:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | declare type UrlType = {
4 | asPath?: string,
5 | back?: Function,
6 | pathname: string,
7 | push?: Function,
8 | pushTo?: Function,
9 | query: Object,
10 | replace?: Function,
11 | replaceTo?: Function
12 | };
13 |
14 | declare type HeadersType = {
15 | accept: string,
16 | 'accpet-encoding': string,
17 | 'accept-language': string,
18 | 'cache-control': string,
19 | connection: string,
20 | cookie: string,
21 | host: string,
22 | pragma: string,
23 | referer: string,
24 | 'upgrade-insecure-requests': string,
25 | 'user-agent': string
26 | };
27 |
28 | declare type Post = {
29 | id: string,
30 | title: string,
31 | votes: number,
32 | createdAt: string,
33 | url: string
34 | };
35 |
--------------------------------------------------------------------------------
/types/next.js.flow:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | declare module 'next' {
4 | declare type NextApp = {
5 | prepare(): Promise,
6 | getRequestHandler(): any,
7 | render(req: any, res: any, pathname: string, query: any): any,
8 | renderToHTML(req: any, res: any, pathname: string, query: string): string,
9 | renderError(err: Error, req: any, res: any, pathname: any, query: any): any,
10 | renderErrorToHTML(err: Error, req: any, res: any, pathname: string, query: any): string,
11 | };
12 | declare module.exports: (...opts: any) => NextApp;
13 | }
14 |
15 | declare module 'next/head' {
16 | declare module.exports: Class>;
17 | }
18 |
19 | declare module 'next/link' {
20 | declare module.exports: Class>;
21 | }
22 |
23 | declare module 'next/error' {
24 | declare module.exports: Class>;
25 | }
26 |
27 | declare module 'next/router' {
28 | declare module.exports: {
29 | route: string,
30 | pathname: string,
31 | query: Object,
32 | onRouteChangeStart: ?(url: string) => void,
33 | onRouteChangeComplete: ?(url: string) => void,
34 | onRouteChangeError: ?(err: Error & { cancelled: boolean }, url: string) => void,
35 | push(url: string, as: ?string): Promise,
36 | replace(url: string, as: ?string): Promise,
37 | };
38 | }
39 |
40 | declare module 'next/document' {
41 | declare export var Head: Class>;
42 | declare export var Main: Class>;
43 | declare export var NextScript: Class>;
44 | declare export default Class> & {
45 | getInitialProps: (ctx: {
46 | pathname: string,
47 | query: any,
48 | req?: any,
49 | res?: any,
50 | jsonPageRes?: any,
51 | err?: any,
52 | }) => Promise,
53 | renderPage(cb: Function): void,
54 | }
55 | }
56 |
--------------------------------------------------------------------------------