├── .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 | ![RAN](https://user-images.githubusercontent.com/694940/29736531-6ab509e8-8a02-11e7-8e61-66e5ea4e29b8.png) 2 | ### RAN : React . GraphQL . Next.js Toolkit 3 | 4 | [![Backers on Open Collective](https://opencollective.com/ran/backers/badge.svg)](https://opencollective.com/ran/) [![Sponsors on Open Collective](https://opencollective.com/ran/sponsors/badge.svg)](https://opencollective.com/ran/) 5 | [![Greenkeeper badge](https://badges.greenkeeper.io/Sly777/ran.svg)](https://greenkeeper.io/) [![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors) [![Join the chat at https://gitter.im/ran-boilerplate/Lobby](https://badges.gitter.im/ran-boilerplate/Lobby.svg)](https://gitter.im/ran-boilerplate/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/Sly777/ran.svg?branch=master)](https://travis-ci.org/Sly777/ran) [![license](https://img.shields.io/github/license/sly777/ran.svg)]()
6 | [![Code Climate](https://codeclimate.com/github/Sly777/ran/badges/gpa.svg)](https://codeclimate.com/github/Sly777/ran) [![Known Vulnerabilities](https://snyk.io/test/github/sly777/ran/badge.svg)](https://snyk.io/test/github/sly777/ran) [![npm](https://img.shields.io/npm/v/ran-boilerplate.svg)](https://www.npmjs.com/package/ran-boilerplate) [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) [![GitHub stars](https://img.shields.io/github/stars/sly777/ran.svg?style=social&label=Stars)](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 | [![Remix on Glitch](https://cdn.glitch.com/2703baf2-b643-4da7-ab91-7ee2a2d00b5b%2Fremix-button.svg)](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 | ![YAY](https://media.giphy.com/media/l0Iy6nmyS5p7hIAso/giphy.gif) 80 | ![YAYY](https://media.giphy.com/media/26vIfscbQhVK7ML5u/giphy.gif) 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 |
44 | {mapFields} 45 |
46 | 51 | {authMethod} 52 | 53 |
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 |
49 |

Add new post

50 | 51 | 52 | Submit 53 |
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 | RAN! 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 | 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 |
20 |

Loading...

21 |
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 | ![create:page](https://media.giphy.com/media/l0Iy6nmyS5p7hIAso/giphy.gif) 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 | ![create:component](https://media.giphy.com/media/26vIfscbQhVK7ML5u/giphy.gif) 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 | ![graphql:play](https://media.giphy.com/media/xT39Dh0URQoc8IUOxW/giphy.gif) 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 | ![RAN](https://user-images.githubusercontent.com/694940/29736531-6ab509e8-8a02-11e7-8e61-66e5ea4e29b8.png) 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 | ![Theme support on RAN!](https://media.giphy.com/media/3o7btVPbDir4D1X3S8/giphy.gif) 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 | ![RAN](https://user-images.githubusercontent.com/694940/29736531-6ab509e8-8a02-11e7-8e61-66e5ea4e29b8.png) 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 | ![RAN](https://user-images.githubusercontent.com/694940/29736531-6ab509e8-8a02-11e7-8e61-66e5ea4e29b8.png) 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 | --------------------------------------------------------------------------------