├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── package.json ├── src ├── assets │ ├── email_templates │ │ └── contactForm.hbs │ ├── graphics │ │ ├── favicon.ico │ │ ├── homePage │ │ │ └── background_img.jpg │ │ ├── logo.png │ │ ├── logo_white.png │ │ └── meetTheTeam │ │ │ └── profilePic.jpg │ └── scss │ │ ├── _config.scss │ │ ├── base │ │ ├── _banner_scroller.scss │ │ ├── _css_grid.scss │ │ ├── _default.scss │ │ ├── _forms.scss │ │ └── _normalize.scss │ │ ├── components │ │ ├── _footer.scss │ │ └── _header.scss │ │ ├── pages │ │ ├── _aboutPage.scss │ │ ├── _homePage.scss │ │ ├── _posts.scss │ │ └── _servicesPage.scss │ │ └── styles.scss ├── client │ ├── actions │ │ └── index.js │ ├── app.js │ ├── client.js │ ├── common │ │ └── forms │ │ │ ├── input-types │ │ │ └── index.js │ │ │ └── validation │ │ │ └── index.js │ ├── components │ │ ├── banners │ │ │ ├── internalTextBanner │ │ │ │ └── index.js │ │ │ └── meetTheTeam │ │ │ │ └── index.js │ │ ├── footer.js │ │ ├── header.js │ │ └── renderHTML │ │ │ └── index.js │ ├── endpoints │ │ └── index.js │ ├── layouts │ │ └── custom_layout.js │ ├── pages │ │ ├── aboutPage.js │ │ ├── blog │ │ │ ├── postPage.js │ │ │ └── postsPage.js │ │ ├── contactPage.js │ │ ├── homePage.js │ │ ├── notFound404Page.js │ │ ├── policies │ │ │ ├── cookiesPolicy.js │ │ │ ├── privacy.js │ │ │ └── termsAndConditions.js │ │ └── servicesPage.js │ ├── reducers │ │ ├── index.js │ │ ├── postReducer.js │ │ └── postsReducer.js │ └── routes.js ├── helpers │ ├── createStore.js │ └── renderer.js └── index.js ├── webConfig.json ├── webpack.client.js └── webpack.server.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | yarn.lock 3 | build 4 | .DS_Store 5 | yarn-error.log -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ashley Bibizadeh 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. -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node build/bundle.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # REACT STARTER KIT 2 | 3 |

4 | 5 |

6 | 7 | Open Source Universal React Redux GraphQL Boilerplate 8 | 9 | [Online Demo](https://react-starter-kit-demo.herokuapp.com) 10 | 11 | # Getting Started 12 | 13 | This repository contains the source code for React-Starter-Kit. This documentation will cover the installation on your machine, project architecture, unit testing and working with the app in general. 14 | 15 | This is a Universal React Redux GraphQL Node app. 16 | 17 | ## YouTube Video Tutorials 18 | 19 | ##### Introduction (Getting started) 20 | 21 | 22 | 23 | 24 | 25 | 26 | ##### Project architecture 27 | 28 | 29 | 30 | 31 | 32 | ## Preview 33 | 34 |

35 | 36 |

37 | 38 | ## Software 39 | 40 | Before proceeding, please ensure you have the following software installed on your computer. 41 | 42 | * Node 43 | * Yarn (optional but recommended) 44 | * Git command line tools 45 | 46 | ### Useful links 47 | 48 | * Download Git CLT - Windows: https://git-scm.com/download/windows Mac: https://git-scm.com/download/mac 49 | * Download Node - https://nodejs.org/en/ 50 | * Download Yarn CLT - https://yarnpkg.com/lang/en/docs/install/ 51 | * Download VSCode - https://code.visualstudio.com/ 52 | 53 | 54 | ## Installation 55 | 56 | Please fork a copy of this repository. Forking a repository allows you to freely experiment with changes without affecting the original project. Alternatively download or clone the master branch. 57 | 58 | 1. Clone the repo to your machine 59 | 60 | ``` 61 | git clone 62 | ``` 63 | 64 | 2. Within terminal or cmd ensure you have changed directory (into the new folder that has been cloned) and install the dependencies 65 | 66 | ``` 67 | cd 68 | yarn install OR npm install 69 | ``` 70 | 71 | 3. Before we can build, run or deploy our app it is important to ensure that the 'webConfig.json' is configured for our environment. Please change 'siteURL' to point to either your local or live url. 72 | 73 | ``` 74 | { 75 | "siteURL": "http://localhost:3000", ... 76 | 77 | OR 78 | 79 | { 80 | "siteURL": "http://mydomain.com", ... 81 | ``` 82 | 83 | 5. You must build the app before you can run it 84 | 85 | ``` 86 | yarn run build OR npm run build 87 | ``` 88 | 89 | 6. Run your build 90 | 91 | ``` 92 | yarn run dev OR npm run dev 93 | ``` 94 | 95 | This should launch the application and start running on: http://localhost:3000/ 96 | 97 | 98 | ## Notes 99 | 100 | For specific versions of dependencies being used please reference the 'package.json' file within the main project directory. 101 | 102 | 103 | ## Project architecture 104 | 105 | Within the main project directory 'src' (source) folder, you will find the following folders: 106 | 107 | 1. 'assets' 108 | 109 | This folder contains important files that are referenced throughout the app. 110 | 111 | 2. 'client' 112 | 113 | This folder contains our source code. 114 | 115 | ## A closer look 116 | 117 | Please read the information below before making any changes to this project. You can reference this to gain an understanding of our design patterns, and help you find/write new code. 118 | 119 | ### Inside the 'src' folder 120 | 121 | 1. 'actions' 122 | 123 | Any actions we create should be exported from this file. (incl async) 124 | 125 | 2. common 126 | 127 | This folder contains code that is shared. For example form inupt types or form validation. 128 | 129 | 3. components 130 | 131 | This folder contains components which are not connected to the store. For example, you can find the header & footer components here. 132 | 133 | 4. endpoints 134 | 135 | We are using the axios library to handle our requests both on the server & client. 136 | 137 | I have created an axios instance with a 'baseURL' for our main API. This means when making a call we only need to supply the route and body of our request. 138 | 139 | Please see the example: 140 | 141 | ``` 142 | api.post(landingPageAPI, body).then(response => { ... 143 | ``` 144 | 145 | Please note: The 'baseURL' is defined within the 'webConfig' file. If you need to make a request to a completely different api, use axios without our instance. 146 | 147 | 5. Layouts 148 | 149 | We use layout components to render our routes. Upon configuration, we can define a unique layout component for any route. This can be useful for landing pages or pages that require unique functionality. 150 | 151 | Please note that our routes will be rendered wherever we place the following code: 152 | 153 | ``` 154 | {renderRoutes(this.props.route.routes)} 155 | ``` 156 | 157 | 6. Pages 158 | 159 | This folder is self-explanatory. 160 | 161 | Please maintain the proper naming conventions. For example, 'namePage'. 162 | 163 | ## Important 164 | 165 | Whilst most of our code is standard, please note the special 'loadData' function that is exported. 166 | 167 | As you know, this is a universal application, which means that we need to configure our pages to run on both the server and client. 168 | 169 | The 'loadData' function is used to tell the server what calls need to be complete before throwing down the initial html to the browser. 170 | 171 | 7. Reducers 172 | 173 | This folder is self-explanatory. 174 | 175 | Please maintain the proper naming conventions. For example, 'nameReducer'. 176 | 177 | Before we create a store, we combine our reducers. Please ensure any new reducers are imported into the main 'index' file within the directory and added to the combine reducers method/object. 178 | 179 | 8. FILE: app.js 180 | 181 | You can imagine this as the default layout of our application. 182 | 183 | 9. FILE: routes.js 184 | 185 | This file is self-explanatory. 186 | 187 | Although we are using 'React Router 4' as this is a universal app, I am using the static router. 188 | 189 | This is because we need to handle two types of routing as it needs to run on both the client and the server. 190 | 191 | More information can be found: 192 | 193 | https://github.com/ReactTraining/react-router/tree/master/packages/react-router-config 194 | 195 | 196 | ## Deployment 197 | 198 | To deploy the app, simply follow the steps: 199 | 200 | 1. install dependencies 201 | 202 | 2. run a build 203 | 204 | 3. start the app via the following command - 'node build/bundle.js' 205 | 206 | 207 | # General info 208 | 209 | ## Send emails: 210 | 211 | We are using 'nodemailer' to handle the process of sending emails. 212 | 213 | We have already intergrated into the project. See 'src/client/pages/contactPage.js' and the '/sendmail' route within our server file. 214 | 215 | For more information, please reference: https://github.com/nodemailer/nodemailer 216 | 217 | ## Fonts: 218 | 219 | Throughout the project we reference a single font family 'gt_walsheim', but have multiple font weights. 220 | 221 | We have the following weights included within the project: 222 | 223 | Ultra Thin: 100 224 | Thin: 200 225 | Light: 300 226 | Regular: 400 227 | Medium: 500 228 | Bold: 700 229 | Ultra Bold: 900 230 | 231 | Please reference 'src/assets/fonts/fonts.css' for more. 232 | 233 | ## Reference a background image within css 234 | 235 | Please ensure you copy all your images into the graphics folder. Whenever you compile the scss, webpack will prefix your path with the root domain defined within the 'webConfig' file. 236 | 237 | To reference an image within css, please use the example below: 238 | 239 | ``` 240 | background-image: url('/assets/graphics/[name].[ext]'); 241 | ``` 242 | 243 | ## Reference a image within HTML/components 244 | 245 | There are a number of different methods that can be used to include graphics within our components. However once you begin using more complex routes, you might find some paths breaking. 246 | 247 | The solution is to use absolute paths. This means that we prefix with the enviroment url. Please reference the example below: 248 | 249 | ``` 250 | 251 | ``` 252 | 253 | Please note: ensure you have imported 'webConfig' before using it. 254 | 255 | ## Carousels 256 | 257 | You may wish to implement a carousel within the website. 258 | 259 | Whilst this is slightly more complex because this is a universal app, please reference the link below. 260 | 261 | This app uses 'react-slick'. The relevant dependencies and css have already been included within the project. 262 | 263 | URL: https://www.npmjs.com/package/react-slick-ssr 264 | 265 | ## Forms: 266 | 267 | We are using 'Redux-form' throughout our application. This has made it simple to apply proper validation (inclu async), two-way data binding (initialize from store), and write cleaner, more reusable code (pre-defined input types & validation etc..). 268 | 269 | - input-type 270 | 271 | We are currently exporting constants for the following field types: 272 | 273 | - Textfield 274 | - Textarea 275 | 276 | Please ensure you export new constants from this file if you require any new field types. It is important to be aware that the styles for these inputs can be found within 'src/assets/scss/base/_forms.scss' and that each have multiple variations in terms of both functionality and design determined by the presence of additional classes. 277 | 278 | Be careful when manipulating the mark-up of these constants, as these field types are used throughout the application. 279 | 280 | These input types can be utilized by passing the relevant const through as a prop to each 'Field' element as a 'component' within 'Redux-form'. 281 | 282 | ## Styles 283 | 284 | We are using SCSS (CSS pre-processor/bracketed version of SASS) to enable us to write cleaner and more reusable css code. 285 | 286 | Our main 'styles.scss' is compiled upon each save, once the project has been started from your terminal/cmd. Whilst the resulting 'styles.css' is the only 'css' called from within the project, the 'scss' version simply contains imports to the partial files created within folders. 287 | 288 | ## CSS Rules 289 | 290 | In order to ensure the integrity of the project and long-term maintainability, please conform the following rules: 291 | 292 | - Media queries should be written/maintained within the relevant 'partial' file. 293 | - REM CSS measurement units: 294 | 295 | We are using rem's to style our app. Whilst you should not use any other measurement unit, it is correct to use ‘px’ for certain properties. For example, you should still use 'px' for setting a border or the width of a media query to ensure more accurate break points. 296 | 297 | The root font size is set to '10px'. This means the calculation required to implement rem’s is simple: (target font size / 10 OR 18 / 10 = 1.8rem). 298 | 299 | - 'config.js' 300 | 301 | Please utilize our configuration partial file wherever possible. This contains global variables for generic/brand colours, keyframes, mixins and more! For consistency please reference these instead of redefining where possible (or add to this file). 302 | 303 | 304 | ## 12 Col Grid Boiler template 305 | 306 | We are using a 12 Column Grid that works as follows: 307 | 308 | The grid has 12 columns 309 | 310 | Each columns width is a % value that can be calculated using the following: 311 | 312 | ``` 313 | Target Columns (Example '6') 314 | Minus Number of Total Columns (12) 315 | Times 100 = 50% 316 | ``` 317 | 318 | Example HTML mark-up for a 2-column layout: 319 | 320 | ``` 321 |
322 | 323 |
324 | Half 325 |
326 | 327 |
328 | Half 329 |
330 | 331 |
332 | ``` 333 | 334 | Each column has a ‘20px’ gutter/ Each column has 10px padding on either side. 335 | 336 | The grid should not be used within areas that require custom mark-up (such as your header/footer) to avoid having to override default styling. This will ensure the integrity of the code. Please DO NOT apply any styling to the grid directly. These should be global classes which are not overridden (ensures you will not break styling in other places). 337 | 338 | To centre your content, simply apply a 'max-width' to the parent wrapper '.grid'. You can either set this globally within the '_responsive-grid' partial file or target it specifically via a custom parent class. 339 | 340 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-starter-kit", 3 | "version": "1.0.0", 4 | "description": "open source universal react redux graphql boilerplate", 5 | "main": "index.js", 6 | "scripts": { 7 | "heroku-postbuild": "webpack --config webpack.server.js && webpack --config webpack.client.js", 8 | "build": "webpack --config webpack.server.js && webpack --config webpack.client.js", 9 | "serve": "node build/bundle.js", 10 | "dev": "npm-run-all --parallel dev:*", 11 | "dev:build-server": "webpack --config webpack.server.js --watch", 12 | "dev:build-client": "webpack --config webpack.client.js --watch", 13 | "dev:server": "nodemon --watch build --exec node build/bundle.js" 14 | }, 15 | "author": "Ashley Bibizadeh", 16 | "license": "MIT", 17 | "dependencies": { 18 | "axios": "0.16.2", 19 | "babel-cli": "6.26.0", 20 | "babel-core": "6.26.0", 21 | "babel-loader": "7.1.2", 22 | "babel-preset-env": "1.6.0", 23 | "babel-preset-es2015": "6.24.1", 24 | "babel-preset-es2017": "6.24.1", 25 | "babel-preset-react": "6.24.1", 26 | "babel-preset-stage-0": "6.24.1", 27 | "body-parser": "^1.18.3", 28 | "classnames": "^2.2.5", 29 | "compression": "1.7.0", 30 | "concurrently": "3.5.0", 31 | "copy-webpack-plugin": "4.5.1", 32 | "express": "4.15.4", 33 | "express-http-proxy": "1.0.6", 34 | "lodash": "4.17.4", 35 | "nodemailer": "^4.6.4", 36 | "nodemailer-express-handlebars": "^3.0.0", 37 | "nodemon": "1.12.0", 38 | "npm-run-all": "4.1.1", 39 | "react": "16.0.0", 40 | "react-addons-css-transition-group": "^15.6.2", 41 | "react-dom": "16.0.0", 42 | "react-helmet": "5.2.0", 43 | "react-redux": "5.0.6", 44 | "react-router-config": "1.0.0-beta.4", 45 | "react-router-dom": "4.2.2", 46 | "react-slick": "^0.22.2", 47 | "redux": "3.7.2", 48 | "redux-form": "^7.3.0", 49 | "redux-thunk": "2.2.0", 50 | "serialize-javascript": "1.4.0", 51 | "webpack": "3.5.6", 52 | "webpack-dev-server": "2.8.2", 53 | "webpack-merge": "4.1.0", 54 | "webpack-node-externals": "1.6.0" 55 | }, 56 | "devDependencies": { 57 | "css-loader": "^0.28.7", 58 | "extract-loader": "^1.0.1", 59 | "file-loader": "^0.11.2", 60 | "node-sass": "^4.5.3", 61 | "sass-loader": "^6.0.6" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/assets/email_templates/contactForm.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |
9 | 12 |
13 |
14 | 15 | 16 | 21 | 26 | 27 | 28 | 33 | 38 | 39 | 40 | 45 | 50 | 51 |
17 |

18 | Name: 19 |

20 |
22 |

23 | {{firstName}} {{lastName}} 24 |

25 |
29 |

30 | Email: 31 |

32 |
34 |

35 | {{email}} 36 |

37 |
41 |

42 | Message: 43 |

44 |
46 |

47 | {{message}} 48 |

49 |
52 |
53 |
54 |
55 |
56 | 57 | -------------------------------------------------------------------------------- /src/assets/graphics/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simpletut/React-Starter-Kit/2caee3a4c9248c46fc4f07c69f8f25c62bfc1f29/src/assets/graphics/favicon.ico -------------------------------------------------------------------------------- /src/assets/graphics/homePage/background_img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simpletut/React-Starter-Kit/2caee3a4c9248c46fc4f07c69f8f25c62bfc1f29/src/assets/graphics/homePage/background_img.jpg -------------------------------------------------------------------------------- /src/assets/graphics/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simpletut/React-Starter-Kit/2caee3a4c9248c46fc4f07c69f8f25c62bfc1f29/src/assets/graphics/logo.png -------------------------------------------------------------------------------- /src/assets/graphics/logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simpletut/React-Starter-Kit/2caee3a4c9248c46fc4f07c69f8f25c62bfc1f29/src/assets/graphics/logo_white.png -------------------------------------------------------------------------------- /src/assets/graphics/meetTheTeam/profilePic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simpletut/React-Starter-Kit/2caee3a4c9248c46fc4f07c69f8f25c62bfc1f29/src/assets/graphics/meetTheTeam/profilePic.jpg -------------------------------------------------------------------------------- /src/assets/scss/_config.scss: -------------------------------------------------------------------------------- 1 | // Base Colors 2 | $txtCol: #4a4a4a; 3 | $black: #010000; 4 | $dark_blue: #003c76; 5 | $white: #ffffff; 6 | $orange: #f5a637; 7 | $red: #fe2424; 8 | 9 | @mixin translateY($e) { 10 | -webkit-transform: translateY($e); 11 | -moz-transform: translateY($e); 12 | transform: translateY($e); 13 | } 14 | 15 | @mixin transition($e) { 16 | -webkit-transition:$e; 17 | -moz-transition:$e; 18 | transition:$e; 19 | } -------------------------------------------------------------------------------- /src/assets/scss/base/_banner_scroller.scss: -------------------------------------------------------------------------------- 1 | // Global 2 | 3 | .banner_scroller { 4 | opacity: 0; 5 | overflow: hidden; 6 | 7 | &.loaded { 8 | opacity: 1; 9 | transition: all .8s ease-in-out; 10 | 11 | img { 12 | display: block; 13 | width: 100%; 14 | } 15 | } 16 | 17 | .slick-slider { 18 | position: relative; 19 | height: auto; 20 | 21 | .slick-slide { 22 | height: auto; 23 | } 24 | 25 | .slick-arrow { 26 | position: absolute; 27 | top: 50%; 28 | @include translateY(-50%); 29 | z-index: 1; 30 | 31 | 32 | &.slick-prev { 33 | left: 1rem; 34 | display: block; 35 | width: auto; 36 | height: auto; 37 | background: transparent; 38 | font-size: 0; 39 | line-height: 0; 40 | color: transparent; 41 | border: none; 42 | outline: none; 43 | cursor: pointer; 44 | 45 | &::after { 46 | content: "\f359"; 47 | font-family: Font Awesome\ 5 Free; 48 | font-size: 3.0rem; 49 | line-height: normal; 50 | font-weight: 400; 51 | color: #ffffff; 52 | } 53 | 54 | } 55 | 56 | &.slick-next { 57 | right: 1rem; 58 | display: block; 59 | width: auto; 60 | height: auto; 61 | background: transparent; 62 | font-size: 0; 63 | line-height: 0; 64 | color: transparent; 65 | border: none; 66 | outline: none; 67 | cursor: pointer; 68 | 69 | &::after { 70 | content: "\f35a"; 71 | font-family: Font Awesome\ 5 Free; 72 | font-size: 3.0rem; 73 | line-height: normal; 74 | font-weight: 400; 75 | color: #ffffff; 76 | } 77 | } 78 | 79 | } 80 | 81 | ul, ul li { 82 | margin: 0; 83 | padding: 0; 84 | list-style-type: none; 85 | } 86 | 87 | ul { 88 | position: absolute; 89 | bottom: 1rem; left: 0; right: 0; 90 | margin: 0 auto; 91 | width: 100%; 92 | text-align: center; 93 | 94 | li { 95 | display: inline-block; 96 | width: auto; 97 | margin: 0 .5rem; 98 | 99 | button { 100 | position: relative; 101 | display: block; 102 | width: 1.5rem; 103 | height: 1.5rem; 104 | background: transparent; 105 | border-radius: 50%; 106 | outline: 0; 107 | font-size: 0; 108 | line-height: 0; 109 | color: transparent; 110 | } 111 | 112 | &.slick-active button { 113 | background: $white; 114 | } 115 | 116 | } 117 | 118 | } 119 | 120 | 121 | } 122 | 123 | } 124 | 125 | 126 | // Custom Banners 127 | 128 | .topBanner_textOverlay { 129 | position: relative; 130 | top: 0; left: 0; 131 | background-attachment: fixed; 132 | background-position: right; 133 | background-size: cover; 134 | background-image: url('/assets/graphics/homePage/background_img.jpg'); 135 | width: 100%; 136 | height: 40.0rem; 137 | 138 | h1 { 139 | position: absolute; 140 | top: 50%; left: 0; right: 0; 141 | margin: 0 auto; 142 | font-size: 5rem; 143 | font-weight: 700; 144 | color: $white; 145 | text-align: center; 146 | } 147 | 148 | } 149 | 150 | 151 | // Meet the team 152 | 153 | .meetTheTeam { 154 | 155 | .item { 156 | padding: 0 10px; 157 | 158 | .profilePic { 159 | display: block; 160 | width: 100%; 161 | 162 | img { 163 | display: block; 164 | width: 100%; 165 | margin: 0; 166 | } 167 | 168 | } 169 | 170 | .details { 171 | padding: 2.0rem 0 0 0; 172 | 173 | .name { 174 | display: block; 175 | width: 100%; 176 | font-size: 2.4rem; 177 | line-height: 2.8rem; 178 | color: $black; 179 | text-align: center; 180 | font-weight: 700; 181 | } 182 | 183 | .jobRole { 184 | display: block; 185 | width: 100%; 186 | font-size: 1.8rem; 187 | line-height: 2.4rem; 188 | color: $black; 189 | text-align: center; 190 | font-weight: 400; 191 | } 192 | 193 | } 194 | 195 | } 196 | 197 | .banner_scroller .slick-slider { 198 | padding-bottom: 6rem; 199 | 200 | .slick-arrow { 201 | 202 | &.slick-prev, 203 | &.slick-next { 204 | top: auto; bottom: 0; left: 0; right: 0; 205 | margin: 0 auto; 206 | transform: none; 207 | height: auto; 208 | width: 3.0rem; 209 | 210 | &::after { 211 | color: $black; 212 | } 213 | 214 | &:hover::after { 215 | color: $red; 216 | } 217 | 218 | &:hover::after, 219 | &::after { 220 | @include transition(all .4s ease-in-out); 221 | } 222 | 223 | } 224 | 225 | 226 | 227 | &.slick-prev { 228 | right: 5rem; 229 | } 230 | 231 | &.slick-next { 232 | left: 5rem; 233 | } 234 | 235 | } 236 | } 237 | 238 | } 239 | 240 | 241 | 242 | @media only screen and (max-width: 767px){ 243 | 244 | .topBanner_textOverlay h1 { 245 | font-size: 2.5rem; 246 | line-height: 3.5rem; 247 | } 248 | 249 | 250 | } -------------------------------------------------------------------------------- /src/assets/scss/base/_css_grid.scss: -------------------------------------------------------------------------------- 1 | /********************************** 2 | * 12 Column grid template 3 | **********************************/ 4 | 5 | /* Apply Global Content Width */ 6 | .grid { 7 | max-width: 114.0rem; 8 | margin: 0 auto; 9 | } 10 | 11 | .column { 12 | float: left; 13 | padding: 0 10px; 14 | margin: 0 auto; 15 | } 16 | 17 | /* - Remove additional padding on child elements (Must be the same value as 'grid_col' padding) */ 18 | .col_child { margin: 0 -10px; } 19 | 20 | /* Default column widths - DO NOT CHANGE */ 21 | .column_1_12 { width: 8.33333%; } 22 | .column_2_12 { width: 16.66667%; } 23 | .column_3_12 { width: 25%;} 24 | .column_4_12 { width: 33.33333%;} 25 | .column_5_12 { width: 41.66667%;} 26 | .column_6_12 { width: 50%; } 27 | .column_7_12 { width: 58.33333%; } 28 | .column_8_12 { width: 66.66667%; } 29 | .column_9_12 { width: 75%; } 30 | .column_10_12 { width: 83.33333%; } 31 | .column_11_12 { width: 91.66667%; } 32 | .column_12_12 { width: 100%; } 33 | 34 | .clear_fix { clear: both; } 35 | 36 | 37 | /********************************** 38 | * Flexbox - Equal height columns 39 | **********************************/ 40 | 41 | .flexbox { 42 | display: -webkit-box; 43 | display: -moz-box; 44 | display: -ms-flexbox; 45 | display: -webkit-flex; 46 | display: flex; 47 | -webkit-box-direction: normal; 48 | -moz-box-direction: normal; 49 | -ms-flex-direction: row; 50 | -webkit-flex-direction: row; 51 | flex-direction: row; 52 | -webkit-box-lines: multiple; 53 | -moz-box-lines: multiple; 54 | -ms-flex-wrap: wrap; 55 | -webkit-flex-wrap: wrap; 56 | flex-wrap: wrap; 57 | -webkit-box-pack: justify; 58 | -moz-box-pack: justify; 59 | -ms-flex-pack: justify; 60 | } 61 | 62 | .flexbox .column { 63 | float: none; 64 | } 65 | 66 | 67 | 68 | /********************************** 69 | * Test 12 Column Grid Overlay 70 | **********************************/ 71 | 72 | .grid.text_grid_overlay { 73 | display: none; 74 | position: fixed; 75 | top: 0; left: 0; right: 0; 76 | margin: 0 auto; 77 | height: 100%; 78 | } 79 | 80 | .grid.text_grid_overlay .column_1_12 { 81 | height: 100%; 82 | position: relative; 83 | background: transparent; 84 | } 85 | 86 | .grid.text_grid_overlay .column_1_12 span { 87 | display: block; 88 | height: 100%; 89 | background: rgba(255, 0, 0,0.8); 90 | } 91 | 92 | 93 | /********************************** 94 | * Responsive 95 | **********************************/ 96 | 97 | 98 | 99 | 100 | @media only screen and (max-width: 1140px) { 101 | .grid { 102 | margin: 0 10px; 103 | } 104 | } 105 | 106 | @media only screen and (max-width: 960px) { 107 | 108 | .column, .flexbox_col { width: 100%; } 109 | 110 | .flexbox { 111 | display: block; 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /src/assets/scss/base/_default.scss: -------------------------------------------------------------------------------- 1 | // Font weights 2 | // regular 400 3 | // bold 700 4 | 5 | html { 6 | font-size: 10px; 7 | } 8 | 9 | body, html, #root { 10 | height: 100%; 11 | } 12 | 13 | *, *:before, *:after { 14 | -webkit-box-sizing: border-box; 15 | -moz-box-sizing: border-box; 16 | box-sizing: border-box; 17 | } 18 | 19 | ::-moz-selection { background: $orange; color: $white;} 20 | ::selection { background: $orange; color: $white; text-shadow: none; } 21 | 22 | .hidden { display:none; } 23 | .clear { clear: both; } 24 | 25 | .float-left { float: left; } 26 | .float-right { float: right; } 27 | 28 | body { 29 | font-family: 'Lato',helvetica,arial,clean,sans-serif; 30 | font-size: 1.5rem; 31 | font-weight: 400; 32 | font-style: normal; 33 | color: $txtCol; 34 | padding: 0; 35 | margin: 0; 36 | } 37 | 38 | a, p, strong { 39 | line-height: 2.4rem; 40 | } 41 | 42 | a { 43 | color: $black; 44 | text-decoration: none; 45 | cursor: pointer; 46 | 47 | &:hover, 48 | &:visited { 49 | color: $orange; 50 | } 51 | 52 | } 53 | 54 | a, a:hover { 55 | @include transition(all .4s ease-in-out); 56 | } 57 | 58 | p { 59 | margin: 0 0 1rem 0; 60 | } 61 | 62 | strong { 63 | font-weight: 700; 64 | } 65 | 66 | .main { 67 | display: block; 68 | width: 100%; 69 | min-height: 50.0rem; 70 | margin: 8rem auto 0; 71 | padding: 0; 72 | } 73 | 74 | .innerPage_ContentArea_minHeight { 75 | height: 100%; 76 | height: calc(100% - 40.0rem); 77 | } 78 | 79 | // headings 80 | 81 | h1 { 82 | font-size: 4.2rem; 83 | line-height: 4.8rem; 84 | color: $black; 85 | font-weight: 400; 86 | display: block; 87 | width: 100%; 88 | margin: 0 0 3.0rem 0; 89 | padding: 0; 90 | text-transform: uppercase; 91 | } 92 | 93 | h2 { 94 | font-size: 2.8rem; 95 | line-height: 3.4rem; 96 | color: $black; 97 | font-weight: 400; 98 | display: block; 99 | width: 100%; 100 | margin: 0 0 1.0rem 0; 101 | padding: 0; 102 | text-transform: uppercase; 103 | } 104 | 105 | h3 { 106 | font-size: 2.4rem; 107 | line-height: 3.0rem; 108 | color: $black; 109 | font-weight: 400; 110 | display: block; 111 | width: 100%; 112 | margin: 0 0 1.0rem 0; 113 | padding: 0; 114 | text-transform: uppercase; 115 | } 116 | 117 | // Page transitions 118 | 119 | .anim-appear { 120 | opacity: 0; 121 | } 122 | 123 | .anim-appear.anim-appear-active{ 124 | opacity: 1; 125 | @include transition(opacity .8s ease-in); 126 | } 127 | 128 | 129 | @media only screen and (max-width: 767px){ 130 | 131 | h2 { 132 | font-size: 2.0rem; 133 | line-height: 2.5rem; 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/assets/scss/base/_forms.scss: -------------------------------------------------------------------------------- 1 | input, select, textarea, button { 2 | font-family: 'Lato',helvetica,arial,clean,sans-serif; 3 | font-size: 1.5rem; 4 | font-weight: 400; 5 | font-style: normal; 6 | color: $txtCol; 7 | outline: none; 8 | } 9 | 10 | input, select, textarea { 11 | outline: none; 12 | border: 1px solid #979797; 13 | } 14 | 15 | input[type=text], input[type=password], input[type=submit], input[type=button], select, textarea, button { 16 | border-radius: 0; 17 | -webkit-appearance: none; 18 | appearance: none; 19 | } 20 | 21 | input[type=radio] { margin: 0 .3rem 0 .6rem; } 22 | 23 | input:focus, select:focus, textarea:focus { 24 | border-color: #455761; 25 | } 26 | 27 | input:hover, select:hover, textarea:hover { 28 | border-color: $black; 29 | } 30 | 31 | .error-label, 32 | .success-label { 33 | clear: both; 34 | display: block; 35 | margin-bottom: 2rem; 36 | padding: 2rem; 37 | color: $white; 38 | font-weight: 500; 39 | } 40 | 41 | .error-label { background: #455761; } 42 | 43 | .success-label { background: #00b3bd; } 44 | 45 | .form_wrap, .form_row { 46 | display: block; 47 | width: 100%; 48 | padding: 0; 49 | margin: 0; 50 | } 51 | 52 | .form_wrap { 53 | padding: 0 10px; 54 | 55 | .form_row { 56 | margin-bottom: 2rem; 57 | 58 | .form_item { 59 | 60 | .form_label { 61 | padding-bottom: 1rem; 62 | 63 | label { 64 | font-size: 1.5rem; 65 | font-weight: 400; 66 | font-style: normal; 67 | color: $txtCol; 68 | } 69 | 70 | } 71 | 72 | .form_input { 73 | input { 74 | width: 100%; 75 | padding: 9px 12px; 76 | font-size: 1.5rem; 77 | font-weight: 400; 78 | font-style: normal; 79 | color: $txtCol; 80 | } 81 | } 82 | 83 | .form_textarea { 84 | textarea { 85 | width: 100%; 86 | padding: 9px 12px; 87 | font-size: 1.5rem; 88 | font-weight: 400; 89 | font-style: normal; 90 | color: $txtCol; 91 | } 92 | } 93 | 94 | .invalid_msg { 95 | display: none; 96 | font-size: 1.3rem; 97 | color: $red; 98 | line-height: normal; 99 | padding: 1rem 0 0; 100 | } 101 | 102 | &.invalid.dirty { 103 | 104 | .invalid_msg { 105 | display: block; 106 | } 107 | 108 | } 109 | 110 | } 111 | 112 | } 113 | 114 | 115 | } 116 | 117 | .form_buttons { 118 | display: block; 119 | width: 100%; 120 | 121 | .btn { 122 | position: relative; 123 | display: block; 124 | font-size: 1.5rem; 125 | line-height: normal; 126 | color: $white; 127 | font-weight: 400; 128 | text-align: center; 129 | max-width: 18rem; 130 | padding: 1.5rem 4.5rem; 131 | margin: auto 0; 132 | background: $orange; 133 | border: none; 134 | cursor: pointer; 135 | 136 | &:hover { 137 | background: $black; 138 | color: $white; 139 | } 140 | 141 | } 142 | 143 | .btn, .btn:hover { 144 | @include transition(all .4s ease-in-out); 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /src/assets/scss/base/_normalize.scss: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | font: inherit; 19 | vertical-align: baseline; 20 | } 21 | /* HTML5 display-role reset for older browsers */ 22 | article, aside, details, figcaption, figure, 23 | footer, header, hgroup, menu, nav, section { 24 | display: block; 25 | } 26 | body { 27 | line-height: 1; 28 | } 29 | ol, ul { 30 | list-style: none; 31 | } 32 | blockquote, q { 33 | quotes: none; 34 | } 35 | blockquote:before, blockquote:after, 36 | q:before, q:after { 37 | content: ''; 38 | content: none; 39 | } 40 | table { 41 | border-collapse: collapse; 42 | border-spacing: 0; 43 | } -------------------------------------------------------------------------------- /src/assets/scss/components/_footer.scss: -------------------------------------------------------------------------------- 1 | footer { 2 | display: inline-block; 3 | width: 100%; 4 | margin: 8rem auto 0; 5 | padding: 7.5rem 10px; 6 | background-color: #343a40; 7 | 8 | .wrap { 9 | display: block; 10 | position: relative; 11 | max-width: 114.0rem; 12 | margin: 0 auto; 13 | padding: 0; 14 | 15 | .clearFloat { 16 | display: inline-block; 17 | width: 100%; 18 | 19 | .left { 20 | float: left; 21 | 22 | .copyright { 23 | font-size: 1.2rem; 24 | line-height: normal; 25 | color: #fff; 26 | } 27 | 28 | } 29 | 30 | .right { 31 | float: right; 32 | 33 | .footer_nav { 34 | display: block; 35 | width: 100%; 36 | text-align: right; 37 | margin: 0 auto 3rem; 38 | 39 | ul, ul li { 40 | margin: 0; 41 | padding: 0; 42 | list-style-type: none; 43 | } 44 | 45 | ul { 46 | li { 47 | display: inline-block; 48 | width: auto; 49 | margin: 0 3rem 0 0; 50 | 51 | a { 52 | position: relative; 53 | font-size: 1.2rem; 54 | line-height: normal; 55 | color: #fff; 56 | text-transform: uppercase; 57 | 58 | &::after { 59 | content: ""; 60 | position: absolute; 61 | width: 0; 62 | height: 1px; 63 | bottom: -.6rem; 64 | left: 0; 65 | background: #f5a637; 66 | @include transition(all .4s ease-in-out); 67 | } 68 | 69 | &:hover { 70 | color: $orange; 71 | 72 | &::after { 73 | width: 100%; 74 | @include transition(all .4s ease-in-out); 75 | } 76 | 77 | } 78 | 79 | } 80 | 81 | 82 | 83 | } 84 | } 85 | 86 | 87 | 88 | } 89 | 90 | .social_icons { 91 | text-align: right; 92 | 93 | ul, ul li { 94 | margin: 0; 95 | padding: 0; 96 | list-style-type: none; 97 | } 98 | 99 | ul { 100 | li { 101 | display: inline-block; 102 | width: auto; 103 | margin: 0 3rem 0 0; 104 | 105 | a { 106 | font-size: 2rem; 107 | line-height: normal; 108 | color: $white; 109 | 110 | &:hover { 111 | color: $orange; 112 | } 113 | 114 | } 115 | } 116 | } 117 | 118 | } 119 | 120 | 121 | } 122 | } 123 | 124 | 125 | 126 | 127 | 128 | 129 | } 130 | 131 | 132 | 133 | 134 | 135 | } 136 | 137 | 138 | @media only screen and (max-width: 767px){ 139 | 140 | footer .wrap .clearFloat { 141 | 142 | .left { 143 | float: none; 144 | width: 100%; 145 | text-align: center; 146 | margin: 0 auto 2rem; 147 | 148 | .copyright { 149 | font-size: 1.6rem; 150 | line-height: normal; 151 | } 152 | 153 | } 154 | 155 | .right { 156 | float: none; 157 | width: 100%; 158 | text-align: center; 159 | 160 | .footer_nav ul li { 161 | display: block; 162 | width: 100%; 163 | padding: 1rem 0; 164 | margin: 0; 165 | 166 | a { 167 | display: block; 168 | width: 100%; 169 | @include transition(none); 170 | font-size: 1.4rem; 171 | line-height: normal; 172 | text-align: center; 173 | 174 | &::after { 175 | display: none; 176 | } 177 | 178 | } 179 | } 180 | 181 | .social_icons { 182 | text-align: center; 183 | 184 | ul li { 185 | margin: 0; 186 | padding: 2rem 2.5rem 0; 187 | } 188 | 189 | } 190 | 191 | } 192 | 193 | } 194 | 195 | 196 | 197 | } -------------------------------------------------------------------------------- /src/assets/scss/components/_header.scss: -------------------------------------------------------------------------------- 1 | header { 2 | position: fixed; 3 | top: 0; left: 0; right: 0; 4 | z-index: 1000; 5 | display: block; 6 | width: 100%; 7 | height: 10.0rem; 8 | margin: 0 auto; 9 | padding: 0; 10 | @include transition(.2s ease-in-out); 11 | 12 | &::after { 13 | content: ''; 14 | position: absolute; 15 | width: 100%; 16 | height: 100%; 17 | background: $white; 18 | display: block; 19 | top: -100%; left: 0; right: 0; 20 | margin: 0 auto; 21 | @include transition(.2s ease-in-out); 22 | z-index: -1; 23 | } 24 | 25 | .wrap { 26 | position: relative; 27 | display: block; 28 | width: 100%; 29 | max-width: 90.0rem; 30 | height: 100%; 31 | margin: 0 auto; 32 | padding: 0 10px; 33 | background-color: transparent; 34 | 35 | .logo_wrap { 36 | display: block; 37 | width: 5rem; 38 | height: 100%; 39 | position: absolute; 40 | top: 0; left: 10px; 41 | @include transition(.2s ease-in-out); 42 | z-index: 20; 43 | 44 | a { 45 | display: block; 46 | height: 100%; 47 | 48 | img { 49 | display: block; 50 | width: 100%; 51 | position: absolute; 52 | top: 50%; 53 | @include translateY(-50%); 54 | @include transition(.2s ease-in-out); 55 | 56 | &.dark { 57 | opacity: 0; 58 | z-index: 0; 59 | } 60 | 61 | &.white { 62 | z-index: 1; 63 | } 64 | } 65 | 66 | } 67 | 68 | } 69 | 70 | .nav_wrap { 71 | position: absolute; 72 | top: 50%; right: 10px; 73 | @include translateY(-50%); 74 | 75 | nav { 76 | 77 | ul, li { 78 | margin: 0; 79 | padding: 0; 80 | list-style-type: none; 81 | } 82 | 83 | ul { 84 | li { 85 | display: inline-block; 86 | margin: 0 4rem 0 0; 87 | 88 | &.last { 89 | margin-right: 0; 90 | } 91 | 92 | a { 93 | position: relative; 94 | font-size: 1.2rem; 95 | line-height: normal; 96 | color: $white; 97 | text-transform: uppercase; 98 | 99 | &::after { 100 | content: ''; 101 | position: absolute; 102 | width: 0; 103 | height: 1px; 104 | bottom: -.6rem; left: 0; 105 | background: $orange; 106 | @include transition(all .4s ease-in-out); 107 | } 108 | 109 | &:hover, &.active { 110 | color: $orange; 111 | 112 | &::after { 113 | width: 100%; 114 | @include transition(all .4s ease-in-out); 115 | } 116 | 117 | } 118 | 119 | 120 | 121 | } 122 | 123 | } 124 | } 125 | 126 | 127 | 128 | } 129 | } 130 | 131 | .mobile { 132 | display: none; 133 | } 134 | 135 | } 136 | 137 | &.scrollActive { 138 | @include transition(.2s ease-in-out); 139 | height: 7rem; 140 | 141 | &::after { 142 | top: 0; 143 | @include transition(.2s ease-in-out); 144 | } 145 | 146 | .wrap { 147 | .logo_wrap { 148 | 149 | a img { 150 | @include transition(.2s ease-in-out); 151 | 152 | &.dark { 153 | opacity: 1; 154 | } 155 | &.white { 156 | opacity: 0; 157 | } 158 | } 159 | 160 | } 161 | 162 | .nav_wrap nav ul li a { 163 | color: $black; 164 | 165 | &:hover, &.active { 166 | color: $orange; 167 | } 168 | 169 | } 170 | 171 | } 172 | 173 | 174 | 175 | } 176 | 177 | } 178 | 179 | @media only screen and (max-width: 767px){ 180 | 181 | header { 182 | height: 7.0rem !important; 183 | 184 | &::after { 185 | top: 0 !important; 186 | z-index: 19; 187 | } 188 | 189 | &.scrollActive { 190 | 191 | .wrap .nav_wrap li a { 192 | color: $white !important; 193 | } 194 | 195 | } 196 | 197 | .wrap { 198 | 199 | .logo_wrap a img.dark { 200 | opacity: 1 !important; 201 | z-index: 20; 202 | } 203 | 204 | .mobile { 205 | display: block; 206 | width: 3rem; 207 | height: 3rem; 208 | position: absolute; 209 | top: 50%; right: 10px; 210 | @include translateY(-50%); 211 | z-index: 20; 212 | 213 | .icon { 214 | display: block; 215 | width: 100%; 216 | height: 100%; 217 | cursor: pointer; 218 | 219 | span { 220 | display: block; 221 | background: $black; 222 | width: 100%; 223 | height: 2px; 224 | position: absolute; 225 | top: 50%; left: 0; 226 | @include translateY(-50%); 227 | @include transition(all .4s ease-in-out); 228 | 229 | &::before, 230 | &::after { 231 | content: ''; 232 | display: block; 233 | width: 100%; 234 | height: 2px; 235 | background: $black; 236 | position: absolute; 237 | left: 0; right: 0; 238 | margin: 0 auto; 239 | @include transition(all .4s ease-in-out); 240 | } 241 | 242 | &::before { 243 | top: -1rem; 244 | } 245 | 246 | &::after { 247 | top: 1rem; 248 | } 249 | 250 | } 251 | 252 | &.mobileNavActive span { 253 | background: transparent; 254 | transform: rotate(-180deg); 255 | @include transition(all .4s ease-in-out); 256 | 257 | &::before, 258 | &::after { 259 | top: 0; 260 | @include transition(all .4s ease-in-out); 261 | } 262 | 263 | &::before { 264 | -webkit-transform: rotate(45deg); 265 | -moz-transform: rotate(45deg); 266 | transform: rotate(45deg); 267 | } 268 | 269 | &::after { 270 | -webkit-transform: rotate(-45deg); 271 | -moz-transform: rotate(-45deg); 272 | transform: rotate(-45deg); 273 | } 274 | } 275 | 276 | 277 | } 278 | } 279 | 280 | .nav_wrap { 281 | opacity: 0; 282 | pointer-events: none; 283 | position: fixed; 284 | left: 0; right: 0; 285 | width: 100%; 286 | height: 100%; 287 | padding: 10.0rem 10px 0; 288 | 289 | &.mobile_active { 290 | opacity: 1; 291 | pointer-events: all; 292 | background: rgba(0,0,0,.9); 293 | z-index: 10; 294 | @include transition(all .4s ease-in-out); 295 | 296 | ul { 297 | 298 | li { 299 | display: block; 300 | width: 100%; 301 | 302 | a { 303 | display: block; 304 | width: 100%; 305 | padding: 2rem 0; 306 | font-size: 2.0rem; 307 | line-height: normal; 308 | text-align: center; 309 | @include transition(none); 310 | 311 | &::after { 312 | display: none; 313 | } 314 | 315 | } 316 | 317 | } 318 | } 319 | 320 | } 321 | } 322 | 323 | 324 | } 325 | 326 | } 327 | 328 | } -------------------------------------------------------------------------------- /src/assets/scss/pages/_aboutPage.scss: -------------------------------------------------------------------------------- 1 | .aboutPage_wrapper { 2 | 3 | .content_block { 4 | margin-bottom: 8rem; 5 | } 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/assets/scss/pages/_homePage.scss: -------------------------------------------------------------------------------- 1 | body.homePage { 2 | .main { 3 | min-height: auto; 4 | } 5 | } 6 | 7 | .homePage_wrap { 8 | 9 | .hero { 10 | position: relative; 11 | top: 0; left: 0; 12 | background-attachment: fixed; 13 | background-position: right; 14 | background-size: cover; 15 | background-image: url('/assets/graphics/homePage/background_img.jpg'); 16 | width: 100%; 17 | height: 100%; 18 | 19 | .content_wrap { 20 | position: absolute; 21 | top: 50%; left: 0; right: 0; 22 | padding: 0 10px; 23 | margin: 0 auto; 24 | @include translateY(-50%); 25 | width: 100%; 26 | max-width: 111.5rem; 27 | 28 | h1 { 29 | font-size: 5.0rem; 30 | line-height: 6.0rem; 31 | color: $white; 32 | font-weight: 700; 33 | text-align: center; 34 | 35 | } 36 | 37 | .msg { 38 | display: block; 39 | width: 100%; 40 | max-width: 60.0rem; 41 | margin: 0 auto 2rem; 42 | font-size: 1.5rem; 43 | line-height: 2.4rem; 44 | color: $white; 45 | font-weight: 400; 46 | text-align: center; 47 | } 48 | 49 | a { 50 | position: relative; 51 | display: block; 52 | font-size: 1.5rem; 53 | line-height: normal; 54 | color: $white; 55 | font-weight: 400; 56 | text-align: center; 57 | border: 1px solid $white; 58 | max-width: 18.0rem; 59 | padding: 1.5rem 0; 60 | margin: 6rem auto 0; 61 | 62 | &::after { 63 | content: ''; 64 | display: block; 65 | position: absolute; 66 | top: 0; left: 0; 67 | width: 0; 68 | height: 100%; 69 | background: $orange; 70 | @include transition(all .4s ease-in-out); 71 | z-index: 1; 72 | } 73 | 74 | span { 75 | display: block; 76 | position: relative; 77 | z-index: 2; 78 | } 79 | 80 | &:hover { 81 | border-color: $orange; 82 | 83 | &::after { 84 | width: 100%; 85 | @include transition(all .4s ease-in-out); 86 | } 87 | } 88 | 89 | 90 | } 91 | 92 | } 93 | 94 | .pageScroll { 95 | position: absolute; 96 | bottom: 3rem; left: 0; right: 0; 97 | margin: 0 auto; 98 | display: block; 99 | width: 5rem; 100 | font-size: 3rem; 101 | line-height: normal; 102 | color: $white; 103 | text-align: center; 104 | } 105 | 106 | 107 | 108 | 109 | } 110 | 111 | .spotlights { 112 | .item_wrap { 113 | display: block; 114 | width: 100%; 115 | text-align: center; 116 | } 117 | } 118 | 119 | .content { 120 | display: block; 121 | width: 100%; 122 | height: 300px; 123 | background: white; 124 | } 125 | 126 | 127 | } 128 | 129 | 130 | @media only screen and (max-width: 767px){ 131 | 132 | 133 | .homePage_wrap { 134 | .hero .content_wrap h1 { 135 | font-size: 2.5rem; 136 | line-height: 3rem; 137 | } 138 | 139 | .spotlights { 140 | .item_wrap { 141 | margin-bottom: 2rem; 142 | } 143 | } 144 | 145 | 146 | } 147 | 148 | 149 | 150 | } -------------------------------------------------------------------------------- /src/assets/scss/pages/_posts.scss: -------------------------------------------------------------------------------- 1 | .posts { 2 | display: block; 3 | width: 100%; 4 | margin: 0 auto; 5 | padding: 0; 6 | 7 | .post { 8 | position: relative; 9 | width: 33.333333%; 10 | height: 55.0rem; 11 | float: left; 12 | padding: 0 10px; 13 | margin: 0 0 2rem; 14 | overflow: hidden; 15 | 16 | .wrap { 17 | position: relative; 18 | height: 100%; 19 | background: $white; 20 | overflow: hidden; 21 | display: block; 22 | max-width: 36.0rem; 23 | margin: 0 auto; 24 | 25 | .img { 26 | position: relative; 27 | display: block; 28 | width: 100%; 29 | height: 24.0rem; 30 | overflow: hidden; 31 | 32 | img { 33 | position: absolute; 34 | top: 50%; left: 50%; 35 | display: block; 36 | width: 60.0rem; 37 | margin: 0 auto; 38 | -webkit-transform: translate(-50%, -50%); 39 | -moz-transform: translate(-50%, -50%); 40 | transform: translate(-50%, -50%); 41 | } 42 | 43 | } 44 | 45 | .headline { 46 | display: block; 47 | width: 100%; 48 | padding: 1rem 0; 49 | 50 | a { 51 | font-size: 2.4rem; 52 | line-height: 2.8rem; 53 | color: $black; 54 | font-weight: 400; 55 | text-align: left; 56 | } 57 | } 58 | 59 | .short_desc { 60 | font-size: 1.5rem; 61 | line-height: 1.6rem; 62 | color: $black; 63 | font-weight: 400; 64 | text-align: left; 65 | } 66 | 67 | .readMore { 68 | position: absolute; 69 | bottom: 0; left: 0; 70 | width: 100%; 71 | height: auto; 72 | padding: 2.5rem 15px; 73 | background: $white; 74 | z-index: 1; 75 | text-align: right; 76 | 77 | &::before { 78 | content: ''; 79 | position: absolute; 80 | display: block; 81 | width: 100%; 82 | height: 3rem; 83 | z-index: 2; 84 | background: -moz-linear-gradient(top, rgba(255,255,255,0) 0%, rgba(255,255,255,0.6) 30%, rgba(255,255,255,1) 100%); 85 | background: -webkit-linear-gradient(top, rgba(255,255,255,0) 0%,rgba(255,255,255,0.6) 30%,rgba(255,255,255,1) 100%); 86 | background: linear-gradient(to bottom, rgba(255,255,255,0) 0%,rgba(255,255,255,0.6) 30%,rgba(255,255,255,1) 100%); 87 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00ffffff', endColorstr='#ffffff',GradientType=0 ); 88 | top: -3rem; left: 0; 89 | } 90 | } 91 | 92 | } 93 | 94 | } 95 | 96 | 97 | 98 | } 99 | 100 | .post { 101 | display: block; 102 | width: 100%; 103 | margin: 0 auto; 104 | padding: 0; 105 | 106 | .post_banner { 107 | display: block; 108 | width: 100%; 109 | margin: 0 auto 5rem; 110 | padding: 0; 111 | 112 | img { 113 | display: block; 114 | width: 100%; 115 | max-width: 50.0rem; 116 | margin: 0 auto; 117 | } 118 | } 119 | 120 | } 121 | 122 | 123 | @media only screen and (max-width: 767px){ 124 | 125 | .posts .post { 126 | width: 100%; 127 | } 128 | 129 | } -------------------------------------------------------------------------------- /src/assets/scss/pages/_servicesPage.scss: -------------------------------------------------------------------------------- 1 | .services_grid { 2 | display: inline-block; 3 | width: 100%; 4 | 5 | .item { 6 | width: 33.333333%; 7 | float: left; 8 | padding: 0 10px; 9 | margin: 0 auto 4rem; 10 | 11 | .icon { 12 | display: block; 13 | width: 100%; 14 | text-align: center; 15 | 16 | i { 17 | font-size: 4.5rem; 18 | color: $black; 19 | } 20 | 21 | } 22 | 23 | .text { 24 | display: block; 25 | width: 100%; 26 | padding: 0; 27 | margin: 0 auto; 28 | 29 | .title { 30 | display: block; 31 | width: 100%; 32 | padding: 2rem 0; 33 | font-size: 2.4rem; 34 | line-height: 2.8rem; 35 | color: $black; 36 | font-weight: 700; 37 | text-align: center; 38 | } 39 | 40 | .desc { 41 | display: block; 42 | width: 100%; 43 | font-size: 1.8rem; 44 | line-height: 2.4rem; 45 | color: $black; 46 | font-weight: 400; 47 | text-align: center; 48 | } 49 | 50 | } 51 | 52 | } 53 | 54 | 55 | 56 | 57 | } 58 | 59 | 60 | @media only screen and (max-width: 767px){ 61 | 62 | .services_grid .item { 63 | width: 100%; 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /src/assets/scss/styles.scss: -------------------------------------------------------------------------------- 1 | @import './config'; 2 | 3 | // Base 4 | @import './base/normalize'; 5 | @import './base/css_grid'; 6 | @import './base/banner_scroller'; 7 | @import './base/forms'; 8 | @import './base/default'; 9 | 10 | // Components 11 | @import './components/header'; 12 | @import './components/footer'; 13 | 14 | // Pages 15 | @import './pages/homePage'; 16 | @import './pages/posts'; 17 | @import './pages/servicesPage'; 18 | @import './pages/aboutPage'; -------------------------------------------------------------------------------- /src/client/actions/index.js: -------------------------------------------------------------------------------- 1 | import {landingPageAPI} from './../endpoints'; 2 | 3 | export const fetchPost = (postID) => async (dispatch, getState, api) => { 4 | 5 | const _query = { 6 | query: `{ 7 | Blog(slug: "${postID}"){ 8 | postTitle 9 | post 10 | imageURL 11 | } 12 | }` 13 | }; 14 | 15 | await api.post(landingPageAPI, _query).then(response => { 16 | dispatch({ 17 | type: 'FETCH_POST', 18 | payload: response.data 19 | }) 20 | }).catch((err) => { 21 | console.log('error', err); 22 | }) 23 | 24 | }; 25 | 26 | export const fetchPosts = () => async (dispatch, getState, api) => { 27 | 28 | const _query = { 29 | query: `{ 30 | allBlogs { 31 | postTitle 32 | shortdescription 33 | slug 34 | imageURL 35 | } 36 | }` 37 | }; 38 | 39 | await api.post(landingPageAPI, _query).then(response => { 40 | dispatch({ 41 | type: 'FETCH_POSTS', 42 | payload: response.data 43 | }) 44 | }).catch((err) => { 45 | console.log('error', err); 46 | }) 47 | 48 | }; 49 | 50 | export const clearPostData = () => (dispatch) => { 51 | dispatch({ 52 | type: 'CLEAR_POST_DATA' 53 | }) 54 | }; 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/client/app.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {renderRoutes} from 'react-router-config'; 3 | import Header from './components/header'; 4 | import Footer from './components/footer'; 5 | 6 | class App extends Component { 7 | 8 | render() { 9 | return ( 10 |
11 |
12 | {renderRoutes(this.props.route.routes)} 13 |
14 |
15 | ); 16 | } 17 | 18 | }; 19 | 20 | export default { 21 | component: App 22 | }; -------------------------------------------------------------------------------- /src/client/client.js: -------------------------------------------------------------------------------- 1 | // Start up point for client side app 2 | import 'babel-polyfill'; 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import {BrowserRouter} from 'react-router-dom'; 6 | import { createStore, applyMiddleware } from 'redux'; 7 | import thunk from 'redux-thunk'; 8 | import { Provider } from 'react-redux'; 9 | import { renderRoutes } from 'react-router-config'; 10 | import axios from 'axios'; 11 | import webConfig from './../../webConfig'; 12 | import Routes from './routes'; 13 | import reducers from './reducers'; 14 | 15 | const axiosInstance = axios.create({ 16 | baseURL: webConfig.axiosInstance_baseURL 17 | }); 18 | 19 | const store = createStore( 20 | reducers, 21 | window.INITIAL_STATE, 22 | applyMiddleware(thunk.withExtraArgument(axiosInstance)) 23 | ); 24 | 25 | ReactDOM.hydrate( 26 | 27 | 28 |
{renderRoutes(Routes)}
29 |
30 |
31 | , document.querySelector('#root')); 32 | 33 | -------------------------------------------------------------------------------- /src/client/common/forms/input-types/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | 4 | // Textfield 5 | export const renderTextField = ({label, input, helperText, meta: {asyncValidating, touched, error}, ...custom}) => ( 6 |
7 | 8 |
9 | 10 |
11 | 12 |
13 | 14 | 15 |
16 | 17 |
18 | {error} 19 |
20 | 21 |
22 | {helperText} 23 |
24 | 25 |
26 | ); 27 | 28 | // Textarea 29 | export const renderTextarea = ({label, input, helperText, meta: {asyncValidating, touched, error}, ...custom}) => ( 30 |
31 | 32 |
33 | 34 |
35 | 36 |
37 | 38 | 39 |
40 | 41 |
42 | {error} 43 |
44 | 45 |
46 | {helperText} 47 |
48 | 49 |
50 | ); -------------------------------------------------------------------------------- /src/client/common/forms/validation/index.js: -------------------------------------------------------------------------------- 1 | 2 | export const validate_contactForm = values => { 3 | 4 | const errors = {} 5 | 6 | const requiredFields = [ 7 | 'firstName', 8 | 'lastName', 9 | 'email', 10 | 'message' 11 | ] 12 | 13 | requiredFields.forEach(field => { 14 | if (!values[field]) { 15 | errors[field] = 'Required' 16 | } 17 | }) 18 | 19 | if ( 20 | values.email && 21 | !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email) 22 | ) { 23 | errors.email = 'Invalid email address' 24 | } 25 | 26 | return errors; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/client/components/banners/internalTextBanner/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | class InternalTextBanner extends Component { 4 | 5 | constructor(props){ 6 | super(props); 7 | } 8 | 9 | render(){ 10 | return( 11 |
12 |

13 | {this.props.Heading} 14 |

15 |
16 | ) 17 | }; 18 | } 19 | 20 | export default InternalTextBanner; -------------------------------------------------------------------------------- /src/client/components/banners/meetTheTeam/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import Slider from 'react-slick'; 3 | import classNames from 'classnames'; 4 | import webConfig from './../../../../../webConfig'; 5 | 6 | class MeetTheTeamSlider extends Component { 7 | 8 | constructor(props){ 9 | super(props); 10 | this.state = { 11 | meetTheTeam_loaded: false 12 | } 13 | } 14 | 15 | componentDidMount(){ 16 | this.setState({meetTheTeam_loaded : true}); 17 | } 18 | 19 | render(){ 20 | var settings = { 21 | dots: false, 22 | infinite: true, 23 | speed: 500, 24 | slidesToShow: 4, 25 | slidesToScroll: 1, 26 | adaptiveHeight: true, 27 | arrows: true, 28 | fade: false, 29 | responsive: [ 30 | { 31 | breakpoint: 1000, 32 | settings: { 33 | slidesToShow: 3 34 | } 35 | }, 36 | { 37 | breakpoint: 800, 38 | settings: { 39 | slidesToShow: 2 40 | } 41 | }, 42 | { 43 | breakpoint: 500, 44 | settings: { 45 | slidesToShow: 1 46 | } 47 | } 48 | ] 49 | }; 50 | 51 | return( 52 |
53 | 54 |
55 | 56 | 57 | 58 |
59 |
60 | 61 |
62 |
63 | 64 | Jane Doe 65 | 66 | 67 | General manager 68 | 69 |
70 |
71 | 72 |
73 |
74 | 75 |
76 |
77 | 78 | Jane Doe 79 | 80 | 81 | General manager 82 | 83 |
84 |
85 | 86 |
87 |
88 | 89 |
90 |
91 | 92 | Jane Doe 93 | 94 | 95 | General manager 96 | 97 |
98 |
99 | 100 |
101 |
102 | 103 |
104 |
105 | 106 | Jane Doe 107 | 108 | 109 | General manager 110 | 111 |
112 |
113 | 114 |
115 |
116 | 117 |
118 |
119 | 120 | Jane Doe 121 | 122 | 123 | General manager 124 | 125 |
126 |
127 | 128 |
129 |
130 | 131 | 132 | 133 |
134 | ) 135 | }; 136 | } 137 | 138 | export default MeetTheTeamSlider; -------------------------------------------------------------------------------- /src/client/components/footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Link, NavLink} from 'react-router-dom'; 3 | import {connect} from 'react-redux'; 4 | 5 | const Footer = () => { 6 | 7 | return ( 8 |
9 |
10 |
11 |
12 |
13 | © 2018 React Starter Kit. All Rights Reserved. 14 |
15 |
16 |
17 |
18 |
    19 |
  • 20 | Terms and conditions 21 |
  • 22 |
  • 23 | Privacy policy 24 |
  • 25 |
  • 26 | Cookies policy 27 |
  • 28 |
  • 29 | Contact 30 |
  • 31 |
32 |
33 |
34 | 46 |
47 |
48 |
49 | 50 |
51 |
52 | ); 53 | }; 54 | 55 | export default Footer; -------------------------------------------------------------------------------- /src/client/components/header.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Link, NavLink} from 'react-router-dom'; 3 | import {connect} from 'react-redux'; 4 | import webConfig from './../../../webConfig'; 5 | import classNames from 'classnames'; 6 | 7 | class Header extends Component { 8 | 9 | constructor(props) { 10 | super(); 11 | this.state = { 12 | vPos : 0, 13 | mobileToggle: false 14 | } 15 | this.listenScrollEvent = this.listenScrollEvent.bind(this); 16 | } 17 | 18 | listenScrollEvent(event) { 19 | this.setState({ 20 | vPos: event.target.body.scrollTop 21 | }); 22 | } 23 | 24 | toggleMobileNav(){ 25 | this.setState({ 26 | mobileToggle: !this.state.mobileToggle 27 | }); 28 | } 29 | 30 | componentDidMount() { 31 | window.addEventListener('scroll', this.listenScrollEvent); 32 | } 33 | componentWillUnmount() { 34 | window.removeEventListener('scroll', this.listenScrollEvent); 35 | } 36 | 37 | render() { 38 | 39 | return ( 40 | 41 |
0, 'mobileNavActive': this.state.mobileToggle })}> 42 | 43 |
44 | 45 |
46 | 47 | Logo 48 | Logo 49 | 50 |
51 | 52 |
53 | 54 | 55 | 56 |
57 | 58 |
59 | 75 |
76 | 77 |
78 | 79 | 80 | 81 | 82 | 83 |
84 | 85 | ); 86 | } 87 | }; 88 | 89 | export default Header; -------------------------------------------------------------------------------- /src/client/components/renderHTML/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | class RenderHTML extends Component { 4 | 5 | constructor(props){ 6 | super(props); 7 | } 8 | 9 | render(){ 10 | return( 11 |
14 |
15 | ) 16 | }; 17 | } 18 | 19 | export default RenderHTML; -------------------------------------------------------------------------------- /src/client/endpoints/index.js: -------------------------------------------------------------------------------- 1 | export const landingPageAPI = '/simple/v1/cjhm2fr577kni01706tr0xvst'; -------------------------------------------------------------------------------- /src/client/layouts/custom_layout.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import classNames from 'classnames'; 3 | import {renderRoutes} from 'react-router-config'; 4 | import Header from './../components/header'; 5 | import Footer from './../components/footer'; 6 | 7 | class CustomLayout extends Component { 8 | 9 | render() { 10 | return ( 11 |
12 |
13 | {renderRoutes(this.props.route.routes)} 14 |
15 |
16 | ); 17 | } 18 | }; 19 | 20 | export default { 21 | component: CustomLayout 22 | }; 23 | 24 | -------------------------------------------------------------------------------- /src/client/pages/aboutPage.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; 3 | import InternalTextBanner from './../components/banners/internalTextBanner'; 4 | import classNames from 'classnames'; 5 | import MeetTheTeamSlider from './../components/banners/meetTheTeam'; 6 | import { Helmet } from 'react-helmet'; 7 | 8 | class About extends Component { 9 | 10 | head(){ 11 | return ( 12 | 13 | {`About - React Starter Kit`} 14 | 15 | ); 16 | } 17 | 18 | render() { 19 | 20 | return ( 21 |
22 | {this.head()} 23 | 24 | 25 |
26 |
27 |
28 |
29 |
30 |

31 | The company 32 |

33 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed venenatis dignissim ultrices. Suspendisse ut sollicitudin nisi. Fusce efficitur nec nunc nec bibendum. Praesent laoreet tortor quis velit facilisis faucibus. Maecenas sollicitudin lectus diam, non vehicula arcu ullamcorper ac. In pharetra, est vitae interdum tincidunt, urna ligula rutrum tellus, sit amet pharetra purus magna eu enim. Sed iaculis imperdiet nisi, et pulvinar mauris gravida maximus. Phasellus vitae lorem at sem mattis volutpat. In eget dictum dui. Quisque nec sapien at massa mattis semper. Ut ac malesuada turpis. Fusce eu nulla vehicula, tincidunt dui ultrices, fermentum felis. Aliquam lectus nisi, feugiat ut aliquet sed, posuere sed libero.

34 |

Pellentesque consectetur massa nec nulla fermentum, at tincidunt elit volutpat. Duis vulputate placerat tortor, sit amet eleifend mauris tincidunt sit amet. Aliquam a maximus ligula. Phasellus nec sapien sed tellus tempor eleifend id a erat. Donec convallis bibendum posuere. Quisque mollis imperdiet malesuada. Suspendisse eget dictum massa.

35 |

Phasellus ac posuere nibh, in finibus nulla. In diam arcu, luctus sit amet condimentum sit amet, tristique et leo. Sed tincidunt justo a porttitor tristique. Nulla eu metus tincidunt, ornare magna ullamcorper, semper lacus. Quisque vitae porttitor odio, id tincidunt tellus. Fusce a suscipit eros. Proin sit amet risus fermentum, lobortis quam sed, consequat velit. Quisque sit amet ultricies lorem. Phasellus scelerisque ipsum eget ipsum posuere suscipit. Proin id molestie mauris, a venenatis ipsum. Quisque efficitur, nibh a dapibus mollis, metus tortor fermentum ante, non pellentesque mi magna non leo. Integer quis sem hendrerit, euismod dolor id, dapibus nulla. Maecenas non ultrices lorem. Pellentesque lobortis, diam eget feugiat lacinia, ante augue ullamcorper tortor, vel ornare sapien lacus pharetra quam.

36 |
37 | 38 |
39 |
40 |
41 |
42 |
43 |
44 | ); 45 | } 46 | 47 | }; 48 | 49 | export default { 50 | component: About 51 | }; -------------------------------------------------------------------------------- /src/client/pages/blog/postPage.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { connect } from 'react-redux'; 3 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; 4 | import { fetchPost,clearPostData } from './../../actions' 5 | import { Helmet } from 'react-helmet'; 6 | import InternalTextBanner from './../../components/banners/internalTextBanner'; 7 | import RenderHTML from './../../components/renderHTML'; 8 | 9 | class Post extends Component { 10 | 11 | componentDidMount(){ 12 | console.log(this.props); 13 | this.props.fetchPost(this.props.match.params.id); 14 | } 15 | componentWillUnmount(){ 16 | this.props.clearPostData(); 17 | } 18 | 19 | render() { 20 | 21 | if(!this.props.postData == false){ 22 | return( 23 |
24 | 25 | {`${this.props.postData.postTitle} - React Starter Kit`} 26 | 27 | 28 | 29 |
30 |
31 |
32 |
33 |
34 | 35 |
36 | 37 |
38 |
39 |
40 |
41 |
42 |
43 | ); 44 | } 45 | 46 | if(this.props.postData == null){ 47 | return ( 48 |
49 | 50 | {`React Starter Kit`} 51 | 52 | 53 | 54 |
55 |
56 |
57 |
58 | 59 |
60 |
61 |
62 |
63 |
64 |
65 | ); 66 | } 67 | 68 | if(this.props.postData == false){ 69 | return ( 70 |
71 | 72 | {`404 not found - React Starter Kit`} 73 | 74 | 75 | 76 |
77 |
78 |
79 |
80 | 81 |
82 |
83 |
84 |
85 |
86 |
87 | ); 88 | } 89 | 90 | 91 | 92 | } 93 | } 94 | 95 | function mapStateToProps(state){ 96 | return { 97 | postData: state.post 98 | }; 99 | }; 100 | 101 | function loadData(store, landingPageID){ 102 | return store.dispatch(fetchPost(landingPageID)); 103 | } 104 | 105 | export default { 106 | loadData, 107 | component: connect(mapStateToProps, { fetchPost, clearPostData })(Post) 108 | }; 109 | 110 | -------------------------------------------------------------------------------- /src/client/pages/blog/postsPage.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { connect } from 'react-redux'; 3 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; 4 | import InternalTextBanner from './../../components/banners/internalTextBanner'; 5 | import RenderHTML from './../../components/renderHTML'; 6 | import {fetchPosts} from './../../actions'; 7 | import { Helmet } from 'react-helmet'; 8 | import {Link} from 'react-router-dom'; 9 | 10 | class Posts extends Component { 11 | 12 | componentDidMount(){ 13 | this.props.fetchPosts(); 14 | } 15 | 16 | renderPosts(){ 17 | if(this.props.pageData != false){ 18 | return this.props.pageData.map((post, index) => { 19 | return ( 20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 | 28 | {post.postTitle} 29 | 30 |
31 |
32 | 33 |
34 | 35 | Read more 36 | 37 |
38 |
39 | 40 |
41 | ); 42 | }) 43 | } 44 | } 45 | 46 | head(){ 47 | return ( 48 | 49 | {`Posts - React Starter Kit`} 50 | 51 | ); 52 | } 53 | 54 | render() { 55 | 56 | if(!this.props.pageData == false){ 57 | return( 58 |
59 | {this.head()} 60 | 61 | 62 |
63 |
64 |
65 |
66 | {this.renderPosts()} 67 |
68 |
69 |
70 |
71 |
72 |
73 | ); 74 | } 75 | 76 | if(this.props.pageData == null){ 77 | return ( 78 |
79 | {this.head()} 80 | 81 | 82 |
83 |
84 |
85 |
86 | 87 |
88 |
89 |
90 | 91 |
92 |
93 |
94 |
95 |
96 | ); 97 | } 98 | 99 | if(this.props.pageData == false){ 100 | return ( 101 |
102 | {this.head()} 103 | 104 | 105 |
106 |
107 |
108 |
109 | 110 |
111 |
112 |
113 | 114 |
115 |
116 |
117 |
118 |
119 | ); 120 | } 121 | 122 | } 123 | } 124 | 125 | function mapStateToProps(state){ 126 | return { 127 | pageData: state.posts.arr 128 | }; 129 | }; 130 | 131 | function loadData(store){ 132 | return store.dispatch(fetchPosts()); 133 | } 134 | 135 | export default { 136 | loadData, 137 | component: connect(mapStateToProps, { fetchPosts })(Posts) 138 | }; 139 | 140 | -------------------------------------------------------------------------------- /src/client/pages/contactPage.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import classNames from 'classnames'; 3 | import { Helmet } from 'react-helmet'; 4 | import {Link, NavLink} from 'react-router-dom'; 5 | import { Field, reduxForm } from 'redux-form'; 6 | import { validate_contactForm as validate } from './../common/forms/validation'; 7 | import { renderTextField, renderTextarea } from './../common/forms/input-types'; 8 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; 9 | import InternalTextBanner from './../components/banners/internalTextBanner'; 10 | 11 | import axios from 'axios'; 12 | 13 | class ContactPage extends Component { 14 | 15 | submit(data){ 16 | console.log(data); 17 | 18 | axios.post('/sendmail', data) 19 | .then(function (response) { 20 | console.log(response); 21 | }) 22 | .catch(function (error) { 23 | console.log(error); 24 | }); 25 | 26 | } 27 | 28 | head(){ 29 | return ( 30 | 31 | {`Contact - React Starter Kit`} 32 | 33 | ); 34 | } 35 | 36 | render() { 37 | const { handleSubmit } = this.props 38 | 39 | return ( 40 | 41 |
42 | {this.head()} 43 | 44 | 45 |
46 |
47 |
48 |
49 | 50 |
51 | 52 |
53 | 54 | 55 |
56 | 61 |
62 | 63 |
64 | 69 |
70 | 71 |
72 | 77 |
78 | 79 |
80 | 85 |
86 | 87 |
88 | 91 |
92 | 93 |
94 | 95 |
96 | 97 |
98 |
99 |
100 |
101 |
102 | 103 |
104 | 105 | ); 106 | } 107 | } 108 | 109 | 110 | ContactPage = reduxForm({ 111 | form: 'contactForm', 112 | validate, 113 | enableReinitialize: true, 114 | })(ContactPage); 115 | 116 | export default { 117 | component: ContactPage 118 | }; -------------------------------------------------------------------------------- /src/client/pages/homePage.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import classNames from 'classnames'; 3 | import { Helmet } from 'react-helmet'; 4 | import {Link, NavLink} from 'react-router-dom'; 5 | 6 | class HomePage extends Component { 7 | 8 | head(){ 9 | return ( 10 | 11 | {`Home Page - React Starter Kit`} 12 | 13 | ); 14 | } 15 | 16 | render() { 17 | return ( 18 |
19 | {this.head()} 20 |
21 |
22 |

23 | Your slogan here 24 |

25 | 26 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Non illo, alias animi iusto neque, sint corrupti? Laudantium, dignissimos id excepturi facilis, facere saepe quasi placeat praesentium ipsa sapiente illo molestiae? 27 | 28 | 29 | Learn more 30 | 31 |
32 |
33 | 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | 43 |
44 |
45 |
46 |
47 |
48 |
49 | 50 |
51 |
52 |
53 |
54 |
55 |
56 | 57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | ); 65 | } 66 | } 67 | 68 | export default { 69 | component: HomePage 70 | }; -------------------------------------------------------------------------------- /src/client/pages/notFound404Page.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; 3 | import InternalTextBanner from './../components/banners/internalTextBanner'; 4 | import { Helmet } from 'react-helmet'; 5 | 6 | 7 | class NotFoundPage extends Component { 8 | 9 | head(){ 10 | return ( 11 | 12 | {`404 not found - React Starter Kit`} 13 | 14 | ); 15 | } 16 | 17 | render(){ 18 | return ( 19 |
20 | {this.head()} 21 | 22 | 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 |
32 |
33 |
34 | ); 35 | } 36 | 37 | } 38 | 39 | export default { 40 | component: NotFoundPage 41 | }; -------------------------------------------------------------------------------- /src/client/pages/policies/cookiesPolicy.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; 3 | import InternalTextBanner from './../../components/banners/internalTextBanner'; 4 | import { Helmet } from 'react-helmet'; 5 | 6 | class CookiesPolicy extends Component { 7 | 8 | head(){ 9 | return ( 10 | 11 | {`Cookies policy - React Starter Kit`} 12 | 13 | ); 14 | } 15 | 16 | render(){ 17 | 18 | return ( 19 | 20 |
21 | {this.head()} 22 | 23 | 24 |
25 |
26 |
27 |
28 |

29 | Cookies policy notice 30 |

31 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed venenatis dignissim ultrices. Suspendisse ut sollicitudin nisi. Fusce efficitur nec nunc nec bibendum. Praesent laoreet tortor quis velit facilisis faucibus. Maecenas sollicitudin lectus diam, non vehicula arcu ullamcorper ac. In pharetra, est vitae interdum tincidunt, urna ligula rutrum tellus, sit amet pharetra purus magna eu enim. Sed iaculis imperdiet nisi, et pulvinar mauris gravida maximus. Phasellus vitae lorem at sem mattis volutpat. In eget dictum dui. Quisque nec sapien at massa mattis semper. Ut ac malesuada turpis. Fusce eu nulla vehicula, tincidunt dui ultrices, fermentum felis. Aliquam lectus nisi, feugiat ut aliquet sed, posuere sed libero.

32 |

Pellentesque consectetur massa nec nulla fermentum, at tincidunt elit volutpat. Duis vulputate placerat tortor, sit amet eleifend mauris tincidunt sit amet. Aliquam a maximus ligula. Phasellus nec sapien sed tellus tempor eleifend id a erat. Donec convallis bibendum posuere. Quisque mollis imperdiet malesuada. Suspendisse eget dictum massa.

33 |

Phasellus ac posuere nibh, in finibus nulla. In diam arcu, luctus sit amet condimentum sit amet, tristique et leo. Sed tincidunt justo a porttitor tristique. Nulla eu metus tincidunt, ornare magna ullamcorper, semper lacus. Quisque vitae porttitor odio, id tincidunt tellus. Fusce a suscipit eros. Proin sit amet risus fermentum, lobortis quam sed, consequat velit. Quisque sit amet ultricies lorem. Phasellus scelerisque ipsum eget ipsum posuere suscipit. Proin id molestie mauris, a venenatis ipsum. Quisque efficitur, nibh a dapibus mollis, metus tortor fermentum ante, non pellentesque mi magna non leo. Integer quis sem hendrerit, euismod dolor id, dapibus nulla. Maecenas non ultrices lorem. Pellentesque lobortis, diam eget feugiat lacinia, ante augue ullamcorper tortor, vel ornare sapien lacus pharetra quam.

34 |
35 |
36 |
37 |
38 |
39 |
40 | ); 41 | 42 | } 43 | } 44 | 45 | export default { 46 | component: CookiesPolicy 47 | }; -------------------------------------------------------------------------------- /src/client/pages/policies/privacy.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; 3 | import InternalTextBanner from './../../components/banners/internalTextBanner'; 4 | import { Helmet } from 'react-helmet'; 5 | 6 | class Privacy extends Component { 7 | 8 | head(){ 9 | return ( 10 | 11 | {`Privacy policy - React Starter Kit`} 12 | 13 | ); 14 | } 15 | 16 | render(){ 17 | 18 | return ( 19 | 20 |
21 | {this.head()} 22 | 23 | 24 |
25 |
26 |
27 |
28 |

29 | Privacy policy notice 30 |

31 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed venenatis dignissim ultrices. Suspendisse ut sollicitudin nisi. Fusce efficitur nec nunc nec bibendum. Praesent laoreet tortor quis velit facilisis faucibus. Maecenas sollicitudin lectus diam, non vehicula arcu ullamcorper ac. In pharetra, est vitae interdum tincidunt, urna ligula rutrum tellus, sit amet pharetra purus magna eu enim. Sed iaculis imperdiet nisi, et pulvinar mauris gravida maximus. Phasellus vitae lorem at sem mattis volutpat. In eget dictum dui. Quisque nec sapien at massa mattis semper. Ut ac malesuada turpis. Fusce eu nulla vehicula, tincidunt dui ultrices, fermentum felis. Aliquam lectus nisi, feugiat ut aliquet sed, posuere sed libero.

32 |

Pellentesque consectetur massa nec nulla fermentum, at tincidunt elit volutpat. Duis vulputate placerat tortor, sit amet eleifend mauris tincidunt sit amet. Aliquam a maximus ligula. Phasellus nec sapien sed tellus tempor eleifend id a erat. Donec convallis bibendum posuere. Quisque mollis imperdiet malesuada. Suspendisse eget dictum massa.

33 |

Phasellus ac posuere nibh, in finibus nulla. In diam arcu, luctus sit amet condimentum sit amet, tristique et leo. Sed tincidunt justo a porttitor tristique. Nulla eu metus tincidunt, ornare magna ullamcorper, semper lacus. Quisque vitae porttitor odio, id tincidunt tellus. Fusce a suscipit eros. Proin sit amet risus fermentum, lobortis quam sed, consequat velit. Quisque sit amet ultricies lorem. Phasellus scelerisque ipsum eget ipsum posuere suscipit. Proin id molestie mauris, a venenatis ipsum. Quisque efficitur, nibh a dapibus mollis, metus tortor fermentum ante, non pellentesque mi magna non leo. Integer quis sem hendrerit, euismod dolor id, dapibus nulla. Maecenas non ultrices lorem. Pellentesque lobortis, diam eget feugiat lacinia, ante augue ullamcorper tortor, vel ornare sapien lacus pharetra quam.

34 |
35 |
36 |
37 |
38 |
39 |
40 | ); 41 | 42 | } 43 | } 44 | 45 | export default { 46 | component: Privacy 47 | }; -------------------------------------------------------------------------------- /src/client/pages/policies/termsAndConditions.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; 3 | import InternalTextBanner from './../../components/banners/internalTextBanner'; 4 | import { Helmet } from 'react-helmet'; 5 | 6 | class TermsAndConditions extends Component { 7 | 8 | head(){ 9 | return ( 10 | 11 | {`Terms and conditions - React Starter Kit`} 12 | 13 | ); 14 | } 15 | 16 | render(){ 17 | 18 | return ( 19 | 20 |
21 | {this.head()} 22 | 23 | 24 |
25 |
26 |
27 |
28 |

29 | Terms and Conditions of Use 30 |

31 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed venenatis dignissim ultrices. Suspendisse ut sollicitudin nisi. Fusce efficitur nec nunc nec bibendum. Praesent laoreet tortor quis velit facilisis faucibus. Maecenas sollicitudin lectus diam, non vehicula arcu ullamcorper ac. In pharetra, est vitae interdum tincidunt, urna ligula rutrum tellus, sit amet pharetra purus magna eu enim. Sed iaculis imperdiet nisi, et pulvinar mauris gravida maximus. Phasellus vitae lorem at sem mattis volutpat. In eget dictum dui. Quisque nec sapien at massa mattis semper. Ut ac malesuada turpis. Fusce eu nulla vehicula, tincidunt dui ultrices, fermentum felis. Aliquam lectus nisi, feugiat ut aliquet sed, posuere sed libero.

32 |

Pellentesque consectetur massa nec nulla fermentum, at tincidunt elit volutpat. Duis vulputate placerat tortor, sit amet eleifend mauris tincidunt sit amet. Aliquam a maximus ligula. Phasellus nec sapien sed tellus tempor eleifend id a erat. Donec convallis bibendum posuere. Quisque mollis imperdiet malesuada. Suspendisse eget dictum massa.

33 |

Phasellus ac posuere nibh, in finibus nulla. In diam arcu, luctus sit amet condimentum sit amet, tristique et leo. Sed tincidunt justo a porttitor tristique. Nulla eu metus tincidunt, ornare magna ullamcorper, semper lacus. Quisque vitae porttitor odio, id tincidunt tellus. Fusce a suscipit eros. Proin sit amet risus fermentum, lobortis quam sed, consequat velit. Quisque sit amet ultricies lorem. Phasellus scelerisque ipsum eget ipsum posuere suscipit. Proin id molestie mauris, a venenatis ipsum. Quisque efficitur, nibh a dapibus mollis, metus tortor fermentum ante, non pellentesque mi magna non leo. Integer quis sem hendrerit, euismod dolor id, dapibus nulla. Maecenas non ultrices lorem. Pellentesque lobortis, diam eget feugiat lacinia, ante augue ullamcorper tortor, vel ornare sapien lacus pharetra quam.

34 |
35 |
36 |
37 |
38 |
39 |
40 | ); 41 | 42 | } 43 | } 44 | 45 | export default { 46 | component: TermsAndConditions 47 | }; -------------------------------------------------------------------------------- /src/client/pages/servicesPage.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; 3 | import InternalTextBanner from './../components/banners/internalTextBanner'; 4 | import { Helmet } from 'react-helmet'; 5 | 6 | class Services extends Component { 7 | 8 | head(){ 9 | return ( 10 | 11 | {`Services - React Starter Kit`} 12 | 13 | ); 14 | } 15 | 16 | render() { 17 | return ( 18 |
19 | {this.head()} 20 | 21 | 22 |
23 |
24 |
25 |
26 | 27 |
28 |
29 | 30 |
31 |
32 | Global coverage 33 | 34 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ipsum enim. 35 | 36 |
37 |
38 | 39 |
40 |
41 | 42 |
43 |
44 | Global coverage 45 | 46 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ipsum enim. 47 | 48 |
49 |
50 | 51 |
52 |
53 | 54 |
55 |
56 | Global coverage 57 | 58 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ipsum enim. 59 | 60 |
61 |
62 | 63 |
64 |
65 | 66 |
67 |
68 | Global coverage 69 | 70 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ipsum enim. 71 | 72 |
73 |
74 | 75 |
76 |
77 | 78 |
79 |
80 | Global coverage 81 | 82 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ipsum enim. 83 | 84 |
85 |
86 | 87 |
88 |
89 | 90 |
91 |
92 | Global coverage 93 | 94 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ipsum enim. 95 | 96 |
97 |
98 | 99 |
100 |
101 |
102 |
103 |
104 |
105 | 106 | ); 107 | } 108 | 109 | }; 110 | 111 | export default { 112 | component: Services 113 | }; -------------------------------------------------------------------------------- /src/client/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { reducer as formReducer } from 'redux-form' 3 | import postsReducer from './postsReducer'; 4 | import postReducer from './postReducer'; 5 | 6 | export default combineReducers({ 7 | form: formReducer, 8 | posts: postsReducer, 9 | post: postReducer 10 | }); -------------------------------------------------------------------------------- /src/client/reducers/postReducer.js: -------------------------------------------------------------------------------- 1 | export default function(state = null, action){ 2 | switch(action.type){ 3 | case 'FETCH_POST': 4 | return action.payload.data.Blog || false; 5 | case 'CLEAR_POST_DATA': 6 | return null 7 | default: 8 | return state; 9 | } 10 | } -------------------------------------------------------------------------------- /src/client/reducers/postsReducer.js: -------------------------------------------------------------------------------- 1 | export default function(state = { 2 | posts: null 3 | }, action){ 4 | switch(action.type){ 5 | case 'FETCH_POSTS': 6 | return {...state, arr: action.payload.data.allBlogs || false}; 7 | default: 8 | return state; 9 | } 10 | } -------------------------------------------------------------------------------- /src/client/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import App from './app'; 3 | import HomePage from './pages/homePage'; 4 | import ContactPage from './pages/contactPage'; 5 | import Services from './pages/servicesPage'; 6 | import About from './pages/aboutPage'; 7 | import Posts from './pages/blog/postsPage'; 8 | import Post from './pages/blog/postPage'; 9 | import TermsAndConditions from './pages/policies/termsAndConditions'; 10 | import Privacy from './pages/policies/privacy'; 11 | import CookiesPolicy from './pages/policies/cookiesPolicy'; 12 | import NotFoundPage from './pages/notFound404Page'; 13 | 14 | export default [ 15 | { 16 | path: '/blog', 17 | ...App, 18 | routes: [ 19 | { 20 | path: '/blog/:id', 21 | ...Post 22 | }, 23 | { 24 | ...Posts 25 | } 26 | ] 27 | }, 28 | { 29 | path: '/Services', 30 | ...App, 31 | routes: [ 32 | { 33 | ...Services 34 | } 35 | ] 36 | }, 37 | { 38 | path: '/about', 39 | ...App, 40 | routes: [ 41 | { 42 | ...About 43 | } 44 | ] 45 | }, 46 | { 47 | path: '/contact', 48 | ...App, 49 | routes: [ 50 | { 51 | ...ContactPage 52 | } 53 | ] 54 | }, 55 | { 56 | path: '/policies/terms', 57 | ...App, 58 | routes: [ 59 | { 60 | ...TermsAndConditions 61 | } 62 | ] 63 | }, 64 | { 65 | path: '/policies/privacy', 66 | ...App, 67 | routes: [ 68 | { 69 | ...Privacy 70 | } 71 | ] 72 | }, 73 | { 74 | path: '/policies/cookies', 75 | ...App, 76 | routes: [ 77 | { 78 | ...CookiesPolicy 79 | } 80 | ] 81 | }, 82 | { 83 | path: '/', 84 | exact: true, 85 | ...App, 86 | routes: [ 87 | { 88 | ...HomePage 89 | } 90 | ] 91 | }, 92 | { 93 | path: '/', 94 | ...App, 95 | routes: [ 96 | { 97 | ...NotFoundPage 98 | } 99 | ] 100 | } 101 | ]; 102 | 103 | -------------------------------------------------------------------------------- /src/helpers/createStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import axios from 'axios'; 4 | import reducers from './../client/reducers'; 5 | import webConfig from './../../webConfig'; 6 | 7 | export default (req) => { 8 | 9 | const axiosInstance = axios.create({ 10 | baseURL: webConfig.axiosInstance_baseURL, 11 | headers: { 12 | cookie: req.get('cookie') || '' 13 | } 14 | }); 15 | 16 | const store = createStore( 17 | reducers, 18 | {}, 19 | applyMiddleware(thunk.withExtraArgument(axiosInstance) 20 | )); 21 | return store; 22 | }; -------------------------------------------------------------------------------- /src/helpers/renderer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {renderToString} from 'react-dom/server'; 3 | import {StaticRouter} from 'react-router-dom'; 4 | import {Provider} from 'react-redux'; 5 | import { renderRoutes } from 'react-router-config'; 6 | import serialize from 'serialize-javascript'; 7 | import {Helmet} from 'react-helmet'; 8 | import Routes from './../client/routes'; 9 | import webConfig from './../../webConfig'; 10 | 11 | export default (req, store, context) => { 12 | const content = renderToString( 13 | 14 | 15 |
{renderRoutes(Routes)}
16 | 17 |
18 | ); 19 | 20 | const helmet = Helmet.renderStatic(); 21 | 22 | return ` 23 | 24 | 25 | ${helmet.meta.toString()} 26 | 27 | 28 | ${helmet.title.toString()} 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
${content}
37 | 40 | 41 | 42 | `; 43 | }; 44 | 45 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import express from 'express'; 3 | import { matchRoutes } from 'react-router-config'; 4 | import proxy from 'express-http-proxy'; 5 | import Routes from './client/routes'; 6 | import renderer from './helpers/renderer'; 7 | import createStore from './helpers/createStore'; 8 | import nodemailer from 'nodemailer'; 9 | import hbs from 'nodemailer-express-handlebars'; 10 | import bodyParser from 'body-parser'; 11 | 12 | const port = process.env.PORT || 3000; 13 | const app = express(); 14 | 15 | app.use(express.static('build')); 16 | app.use(bodyParser.json()); 17 | app.use(bodyParser.urlencoded({ extended: true })); 18 | 19 | app.get(['/*/:param', '*'], (req, res) => { 20 | 21 | const ParamValue = req.params.param ? req.params.param : null; 22 | 23 | const store = createStore(req); 24 | 25 | const promises = matchRoutes(Routes, req.path) 26 | .map(({ route }) => { 27 | return route.loadData ? route.loadData(store, ParamValue) : null; 28 | }) 29 | .map(promise => { 30 | if (promise) { 31 | return new Promise((resolve, reject) => { 32 | promise.then(resolve).catch(resolve); 33 | }); 34 | } 35 | }); 36 | 37 | Promise.all(promises).then(() => { 38 | const context = {}; 39 | const content = renderer(req, store, context); 40 | 41 | if(context.url){ 42 | return res.redirect(301, context.url); 43 | } 44 | 45 | // check if 404 46 | if(context.notFound){ 47 | res.status(404); 48 | } 49 | res.send(content); 50 | }); 51 | 52 | }); 53 | 54 | app.post('/sendmail', (req, response) => { 55 | 56 | var mailer = nodemailer.createTransport({ 57 | service: 'gmail', 58 | auth: { 59 | user: '', 60 | pass: '' 61 | } 62 | }); 63 | 64 | mailer.use('compile', hbs({ 65 | viewPath: 'build/assets/email_templates', 66 | extName: '.hbs' 67 | })); 68 | 69 | mailer.sendMail({ 70 | from: '', 71 | to: '', 72 | subject: 'Contact Form', 73 | template: 'contactForm', 74 | context: { 75 | firstName: req.body.firstName, 76 | lastName: req.body.lastName, 77 | email: req.body.email, 78 | message: req.body.message 79 | } 80 | }, function(err, res){ 81 | if(err){ 82 | console.log(err) 83 | response.status(500).send('500 - Internal Server Error') 84 | } 85 | response.status(200).send('200 - The request has succeeded.') 86 | }); 87 | 88 | }); 89 | 90 | app.listen(port, () => { 91 | console.log(`Running on Port ${port}`); 92 | }); -------------------------------------------------------------------------------- /webConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "siteURL": "http://localhost:3000", 3 | "axiosInstance_baseURL": "https://api.graphcms.com" 4 | } -------------------------------------------------------------------------------- /webpack.client.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webConfig = require('./webConfig'); 3 | 4 | module.exports = { 5 | // Tell webpack the root file of our 6 | // server application 7 | entry: ['./src/client/client.js', './src/assets/scss/styles.scss'], 8 | 9 | // Tell webpack where to put the output file 10 | // that is generated 11 | output: { 12 | filename: 'client_bundle.js', 13 | path: path.resolve(__dirname, 'build'), 14 | publicPath: '/build' 15 | }, 16 | 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.js?$/, 21 | loader: 'babel-loader', 22 | exclude: '/node_modules/', 23 | options: { 24 | presets: [ 25 | 'react', 'stage-0', ['env', { 26 | target: { browsers: ['last 2 versions']} 27 | }] 28 | ] 29 | } 30 | }, 31 | { 32 | test: /\.scss$/, 33 | use: [ 34 | { 35 | loader: 'file-loader', 36 | options: { 37 | name: '[name].min.css', 38 | outputPath: 'assets/css/' 39 | } 40 | }, 41 | { 42 | loader: 'extract-loader' 43 | }, 44 | { 45 | loader: 'css-loader', 46 | options: { 47 | minimize: true, 48 | url: true, 49 | root: webConfig.siteURL 50 | } 51 | }, 52 | { 53 | loader: 'sass-loader' 54 | } 55 | ] 56 | } 57 | ] 58 | } 59 | 60 | }; 61 | 62 | 63 | -------------------------------------------------------------------------------- /webpack.server.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpackNodeExternals = require('webpack-node-externals'); 3 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 4 | 5 | module.exports = { 6 | // Inform webpack that we're building a bundle 7 | // for nodeJS, rather then for the browser 8 | target: 'node', 9 | 10 | // Tell webpack the root file of our 11 | // server application 12 | entry: './src/index.js', 13 | 14 | // Tell webpack where to put the output file 15 | // that is generated 16 | output: { 17 | filename: 'bundle.js', 18 | path: path.resolve(__dirname, 'build'), 19 | publicPath: '/build' 20 | }, 21 | 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.js?$/, 26 | loader: 'babel-loader', 27 | exclude: '/node_modules/', 28 | options: { 29 | presets: [ 30 | 'react', 'stage-0', ['env', { 31 | target: { browsers: ['last 2 versions']} 32 | }] 33 | ] 34 | } 35 | }, 36 | ] 37 | }, 38 | plugins: [ 39 | new CopyWebpackPlugin([ 40 | { from: 'src/assets/graphics', to: 'assets/graphics' }, 41 | { from: 'src/assets/email_templates', to: 'assets/email_templates' } 42 | ]) 43 | ], 44 | 45 | // Tell webpack not to bundle any libraries that exist in the 'node_modules' folder 46 | // into the server bundle 47 | externals: [webpackNodeExternals()] 48 | 49 | }; --------------------------------------------------------------------------------