├── .eslintignore ├── .eslintrc ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENCE ├── README.md ├── generators ├── action │ └── index.js ├── app │ ├── index.js │ └── templates │ │ ├── _layout.tsx │ │ ├── _package.json │ │ └── src │ │ ├── .babelrc │ │ ├── .eslintignore │ │ ├── .eslintrc │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── LICENCE │ │ ├── README.md │ │ ├── api │ │ └── postsApi.js │ │ ├── app-constants │ │ └── index.ts │ │ ├── components │ │ ├── global │ │ │ ├── commentItem │ │ │ │ ├── index.tsx │ │ │ │ └── styles.scss │ │ │ ├── commentList │ │ │ │ ├── index.tsx │ │ │ │ └── styles.scss │ │ │ ├── customErrorBoundary │ │ │ │ ├── fallbackComponent.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── styles.scss │ │ │ ├── customnprogress │ │ │ │ └── index.tsx │ │ │ ├── layout │ │ │ │ └── head.tsx │ │ │ ├── postItem │ │ │ │ ├── index.tsx │ │ │ │ └── styles.scss │ │ │ ├── postList │ │ │ │ ├── index.tsx │ │ │ │ └── styles.scss │ │ │ └── snazzyButton │ │ │ │ ├── index.tsx │ │ │ │ ├── snazzyButton.stories.tsx │ │ │ │ └── styles.scss │ │ └── index.ts │ │ ├── config │ │ ├── custom-environment-variables.js │ │ ├── default.js │ │ ├── development.js │ │ ├── production.js │ │ └── testing.js │ │ ├── contexts │ │ └── index.ts │ │ ├── enums │ │ └── index.ts │ │ ├── hocs │ │ └── index.ts │ │ ├── hooks │ │ └── index.ts │ │ ├── i18n.js │ │ ├── lib │ │ ├── config.shim.js │ │ └── withI18next.js │ │ ├── models │ │ ├── comment.d.ts │ │ ├── dispatchable.d.ts │ │ ├── index.d.ts │ │ ├── loadable.d.ts │ │ └── post.d.ts │ │ ├── next.config.js │ │ ├── pages │ │ ├── _app.js │ │ ├── _document.js │ │ ├── about │ │ │ ├── index.tsx │ │ │ └── styles.scss │ │ ├── index │ │ │ ├── index.tsx │ │ │ └── styles.scss │ │ ├── post │ │ │ ├── index.tsx │ │ │ └── styles.scss │ │ └── posts │ │ │ ├── index.tsx │ │ │ └── styles.scss │ │ ├── redux-store │ │ ├── createStore.ts │ │ ├── posts │ │ │ ├── actions.ts │ │ │ ├── constants.ts │ │ │ ├── reducer.ts │ │ │ ├── sagas.ts │ │ │ ├── selectors.ts │ │ │ └── state.ts │ │ ├── rootReducer.ts │ │ ├── rootSaga.ts │ │ └── storeState.ts │ │ ├── server.js │ │ ├── static │ │ ├── favicon.ico │ │ ├── images │ │ │ ├── analyticsfire-logo.svg │ │ │ └── the-team.JPG │ │ └── locales │ │ │ └── en │ │ │ ├── about.json │ │ │ ├── common.json │ │ │ └── home.json │ │ ├── styles │ │ ├── _mixins.scss │ │ ├── _variables.scss │ │ ├── antd-custom.less │ │ └── main.scss │ │ ├── tests │ │ └── units │ │ │ ├── components │ │ │ ├── __snapshots__ │ │ │ │ └── activeLink.test.js.snap │ │ │ └── activeLink.test.js │ │ │ ├── jest.config.js │ │ │ ├── pages │ │ │ ├── __snapshots__ │ │ │ │ ├── about.test.js.snap │ │ │ │ └── home.test.js.snap │ │ │ ├── about.test.js │ │ │ └── home.test.js │ │ │ └── setup │ │ │ ├── assetsTransformer.js │ │ │ └── index.js │ │ ├── tsconfig.json │ │ ├── tslint.json │ │ ├── typings │ │ ├── react-redux.d.ts │ │ └── storybook.react.d.ts │ │ └── utils │ │ └── index.ts ├── component │ ├── index.js │ └── templates │ │ ├── _component.js │ │ ├── _i18n.json │ │ ├── _styles.scss │ │ └── _test.js ├── context │ ├── index.js │ └── templates │ │ └── _context.ts ├── enum │ ├── index.js │ └── templates │ │ └── _enum.ts ├── hoc │ ├── index.js │ └── templates │ │ └── _hoc.ts ├── hook │ ├── index.js │ └── templates │ │ └── _hook.ts ├── model │ ├── index.js │ └── templates │ │ └── _model.ts ├── page │ ├── index.js │ └── templates │ │ ├── _i18n.json │ │ ├── _index.js │ │ ├── _page.js │ │ ├── _styles.scss │ │ └── _test.js └── store │ ├── index.js │ └── templates │ ├── _actions.ts │ ├── _constants.ts │ ├── _reducer.ts │ ├── _sagas.ts │ ├── _selectors.ts │ ├── _state.ts │ └── _test.js ├── package-lock.json └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | 2 | /node_modules/** 3 | /templates/** -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "airbnb", 4 | ], 5 | "plugins": ["react", "prettier", "standard", "react-hooks"], 6 | "parserOptions": { 7 | "sourceType": "module", 8 | "ecmaFeatures": { 9 | "jsx": false, 10 | } 11 | }, 12 | "env": { 13 | "es6": true, 14 | "node": true 15 | }, 16 | "rules": { 17 | "prettier/prettier": ["error", { 18 | "singleQuote": true, 19 | }], 20 | "react-hooks/rules-of-hooks": "error", 21 | "react-hooks/exhaustive-deps": "warn", 22 | "no-unused-vars": "error", 23 | "no-unused-expressions": "error", 24 | "no-undef": "error", 25 | "no-undefined": "error", 26 | "comma-dangle": [2, "always-multiline"], 27 | "max-classes-per-file": ["warn", 1], 28 | "arrow-parens": "off" 29 | } 30 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .vscode 3 | .idea 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | _*.js 2 | _*.scss 3 | _*.json 4 | generators/app/templates/src/LICENCE -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "tabWidth": 2, 4 | "trailingComma": "all", 5 | "printWidth": 120, 6 | "bracketSpacing": true 7 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.background": "#0F3429", 4 | "titleBar.activeBackground": "#154939", 5 | "titleBar.activeForeground": "#F3FCF9" 6 | } 7 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #### 2.1.10 (2019-09-17) 2 | 3 | ##### Other Changes 4 | 5 | * store ([0f40378e](https://github.com/ElectronHacked/nextjs-typescript-antd/commit/0f40378e764da535903fadf628039cfaf2ca6a2a)) 6 | 7 | #### 2.1.9 (2019-09-17) 8 | 9 | #### 2.1.8 (2019-08-20) 10 | 11 | #### 2.1.7 (2019-08-20) 12 | 13 | #### 2.1.6 (2019-08-20) 14 | 15 | #### 2.1.5 (2019-08-20) 16 | 17 | #### 2.1.4 (2019-08-19) 18 | 19 | #### 2.1.3 (2019-08-19) 20 | 21 | #### 2.1.2 (2019-08-19) 22 | 23 | #### 2.1.1 (2019-08-19) 24 | 25 | ##### Other Changes 26 | 27 | * hoc subgenerator ([657f24ba](https://github.com/ElectronHacked/nextjs-typescript-antd/commit/657f24ba25d2f422e2055d9c332bc01e437eef47)) 28 | * context subgenerator ([4a277008](https://github.com/ElectronHacked/nextjs-typescript-antd/commit/4a277008a45b814a8b45576cfd4246520357ee4a)) 29 | 30 | ### 2.1.0 (2019-08-17) 31 | 32 | ##### Other Changes 33 | 34 | * hook subgenerator ([0312662e](https://github.com/ElectronHacked/nextjs-typescript-antd/commit/0312662e71c4649577d869550addb168631a06af)) 35 | 36 | #### 2.0.2 (2019-08-17) 37 | 38 | ##### Other Changes 39 | 40 | * 120 ([b6ce3067](https://github.com/ElectronHacked/nextjs-typescript-antd/commit/b6ce3067eb612164b602b99d238a54bddb961ca6)) 41 | 42 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Boxfusion Pty (Ltd) 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # A scaffolder for ReactJS using NextJS, TypeScript & Ant Design 4 | 5 | This yeoman generator will build different React components, creating a skeleton for the different files. 6 | 7 | # Requirements 8 | 9 | This is a Yeoman generator. You need to install Yeoman, NodeJS and npm to install the generator and its dependencies. Make sure you have all installed globally. 10 | 11 | First, download and install NodeJS and npm. More information about NodeJS / npm: https://nodejs.org/ 12 | 13 | Second, install Yeoman. More information about Yeoman: http://yeoman.io/ 14 | 15 | # Installation 16 | 17 | ``` 18 | # install yo 19 | $ npm install -g yo 20 | 21 | # install a generator 22 | $ npm install -g generator-nextjs-typescript-antd 23 | ``` 24 | 25 | # Usage 26 | 27 | ``` 28 | $ yo nextjs-typescript-antd 29 | ``` 30 | 31 | # Table of Contents 32 | 33 | * [Questions? Feedback?](#questions-feedback) 34 | * [Folder Structure](#folder-structure) 35 | * [Available Scripts](#available-scripts) 36 | * [npm run dev](#npm-run-dev) 37 | * [npm run build](#npm-run-build) 38 | * [npm run build](#npm-run-build) 39 | * [npm run export](#npm-run-export) 40 | * [Available Generators](#available-generators) 41 | * [yo nextjs-typescript-antd](#yo-nextjs-typescript-antd) 42 | * [yo nextjs-typescript-antd:page](#yo-nextjs-typescript-antdpage) 43 | * [yo nextjs-typescript-antd:component](#yo-nextjs-typescript-antdcomponent) 44 | * [yo nextjs-typescript-antd:store](#yo-nextjs-typescript-antdstore) 45 | * [yo nextjs-typescript-antd:action](#yo-nextjs-typescript-antdaction) 46 | * [yo nextjs-typescript-antd:enum](#yo-nextjs-typescript-antdenum) 47 | * [yo nextjs-typescript-antd:hoc](#yo-nextjs-typescript-antdhoc) 48 | * [yo nextjs-typescript-antd:hook](#yo-nextjs-typescript-antdhook) 49 | * [yo nextjs-typescript-antd:context](#yo-nextjs-typescript-antdcontext) 50 | * [yo nextjs-typescript-antd:model](#yo-nextjs-typescript-antdmodel) 51 | * [Changelog](#changelog-generator) 52 | * [Release and Publish](#release-and-publish) 53 | 54 | # Questions? Feedback? 55 | 56 | Check out [Next.js FAQ & docs](https://github.com/zeit/next.js#faq) or [let us know](https://github.com/ElectronHacked/nextjs-typescript-antd/issues) your feedback. 57 | 58 | # Folder Structure 59 | 60 | After creating an app, it should look something like: 61 | 62 | ``` 63 | my-app/ 64 | api/ 65 | postsApi.js 66 | components/ 67 | global/ 68 | commentItem/ 69 | index.tsx 70 | styles.scss 71 | commentList/ 72 | index.tsx 73 | styles.scss 74 | customNProgress/ 75 | index.tsx 76 | styles.scss 77 | layout/ 78 | index.tsx 79 | styles.scss 80 | postItem/ 81 | index.tsx 82 | styles.scss 83 | postList/ 84 | index.tsx 85 | styles.scss 86 | config/ 87 | custom-environment-variables.js 88 | default.js 89 | development.js 90 | production.js 91 | testing.js 92 | constants/ 93 | index.ts 94 | lib/ 95 | withI18next.js 96 | config.shim.js 97 | models/ 98 | comment.d.ts 99 | post.d.ts 100 | index.d.ts 101 | loadable.d.ts 102 | dispatchable.d.ts 103 | pages/ 104 | about/ 105 | index.tsx 106 | styles.scss 107 | index/ 108 | index.tsx 109 | styles.scss 110 | post/ 111 | index.tsx 112 | styles.scss 113 | posts/ 114 | index.tsx 115 | styles.scss 116 | _app.js 117 | _document.js 118 | redux/ 119 | posts/ 120 | actions.ts 121 | constants.ts 122 | payloads.ts 123 | sagas.ts 124 | selectors.ts 125 | state.ts 126 | createStore.ts 127 | rootReducer.ts 128 | rootSaga.ts 129 | storeState.ts 130 | static/ 131 | favicon.ico 132 | locales/ 133 | en/ 134 | images/ 135 | styles/ 136 | vendors/ 137 | _mixins.scss 138 | _variables.scss 139 | antd-custom.less 140 | main.scss 141 | tests/ 142 | units/ 143 | components/ 144 | jest.config.js 145 | pages/ 146 | setup/ 147 | index.js 148 | assetsTransformer.js 149 | typings/ 150 | react-redux.d.ts 151 | .babelrc 152 | .eslintignore 153 | .eslintrc 154 | .gitignore 155 | .prettierrc 156 | README.md 157 | i18n.js 158 | .babelrc 159 | next.config.js 160 | package.json 161 | ``` 162 | 163 | # Available Scripts 164 | 165 | ### `npm run dev` 166 | 167 | Runs the app in the development mode.
168 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 169 | 170 | The page will reload if you make edits.
171 | You will also see any errors in the console. 172 | 173 | ### `npm run build` 174 | 175 | Builds the app for production to the `.next` folder.
176 | It correctly bundles React in production mode and optimizes the build for the best performance. 177 | 178 | ### `npm run start` 179 | 180 | Starts the application in production mode. 181 | The application should be compiled with `next build` first. 182 | 183 | See the section in Next docs about [deployment](https://github.com/zeit/next.js/wiki/Deployment) for more information. 184 | 185 | ### `npm run export` 186 | 187 | Then you have a static version of your app in the out directory. 188 | 189 | You can also customize the output directory. For that `run next export -h` for the help. 190 | 191 | Now you can deploy the out directory to any static hosting service. 192 | 193 | # Available Generators 194 | 195 | ### `yo nextjs-typescript-antd` 196 | 197 | It will prompt you for the details of your new project `nextjs-typescript-antd` project 198 | 199 | ``` 200 | $ yo nextjs-typescript-antd 201 | Initializing... 202 | ? Your project name rect-next-project 203 | ? Your project display name React Next Project 204 | ? What's your full name User Name 205 | ? What's your email address username@email.com 206 | create package.json 207 | create .babelrc 208 | create .eslintignore 209 | create .eslintrc 210 | create .gitattributes 211 | create .gitignore 212 | create .prettierrc 213 | ... 214 | ``` 215 | 216 | ### `yo nextjs-typescript-antd:page` 217 | 218 | It will prompt you for the name and the title of your new page. 219 | 220 | ``` 221 | $ yo nextjs-typescript-antd:page --force 222 | ? Page name User 223 | ? Page title User Details 224 | ? Would you like to create a store for this page? Yes 225 | ? Store name User 226 | create pages\user\index.tsx 227 | create pages\user\styles.scss 228 | create static\locales\en\user.json 229 | create tests\units\pages\user.test.js 230 | force components\global\layout\index.tsx 231 | force server.js 232 | create redux\user\actions.ts 233 | create redux\user\constants.ts 234 | create redux\user\payloads.ts 235 | create redux\user\reducer.ts 236 | create redux\user\sagas.ts 237 | create redux\user\selectors.ts 238 | create redux\user\state.ts 239 | force redux\rootReducer.ts 240 | force redux\rootSaga.ts 241 | force redux\storeState.ts 242 | ``` 243 | Sometimes you might want to have your page nested within another page. To achieve this, all you need to do is: 244 | ``` 245 | $ yo nextjs-typescript-antd:page --force 246 | ? Page name forgot password 247 | ? Page title Forgot password 248 | ? Is this a nested page? Yes 249 | ? Select the parent page 250 | about 251 | > account 252 | index 253 | people 254 | post 255 | posts 256 | (Move up and down to reveal more choices) 257 | ``` 258 | then after selecting `account` as the parent page, your`forgot password` will be generated within the `account` folder. (Please note that you would have created the `account` page earlier). Below is the final output: 259 | 260 | 261 | 262 | ``` 263 | $ yo nextjs-typescript-antd:page --force 264 | ? Page name forgot password 265 | ? Page title Forgot password 266 | ? Is this a nested page? Yes 267 | ? Select the parent page account 268 | ? Would you like to create a store for this page? No 269 | create pages\account\forgot-password\index.tsx 270 | create pages\account\forgot-password\styles.scss 271 | create static\locales\en\pages\account\forgot-password.json 272 | create tests\units\pages\account\forgot-password.test.js 273 | force components\global\layout\index.tsx 274 | force server.js 275 | ``` 276 | 277 | Note how the page has been named `forgot-password` even though you would have entered `forgot password`. The same would have been the case even if you had entered something like `Forgot Password`, `forgotPassword` or `ForgotPassword` 278 | 279 | You can choose that a the redux `store` be created when you create a page. To achieve this, just answer `y` to the question `Would you like to create a store for this page?`. And the output would be something like below: 280 | ``` 281 | $ yo nextjs-typescript-antd:page --force 282 | ? Page name Forgot Password 283 | ? Page title Forgot password 284 | ? Is this a nested page? Yes 285 | ? Select the parent page account 286 | ? Would you like to create a store for this page? Yes 287 | ? Store name (ForgotPassword) 288 | ``` 289 | Notice how the default Store name is `ForgotPassword`. The same would have been the case even if you had entered something like `forgot Password`, `forgotPassword` or `forgot-password` for the name of the page . And the final output will be something like 290 | 291 | ``` 292 | $ yo nextjs-typescript-antd:page --force 293 | ? Page name Forgot Password 294 | ? Page title Forgot password 295 | ? Is this a nested page? Yes 296 | ? Select the parent page account 297 | ? Would you like to create a store for this page? Yes 298 | ? Store name ForgotPassword 299 | force pages\account\forgot-password\index.tsx 300 | force pages\account\forgot-password\styles.scss 301 | force static\locales\en\pages\account\forgot-password.json 302 | force tests\units\pages\account\forgot-password.test.js 303 | force components\global\layout\index.tsx 304 | force server.js 305 | create redux-store\forgotPassword\actions.ts 306 | create redux-store\forgotPassword\constants.ts 307 | create redux-store\forgotPassword\reducer.ts 308 | create redux-store\forgotPassword\sagas.ts 309 | create redux-store\forgotPassword\selectors.ts 310 | create redux-store\forgotPassword\state.ts 311 | force redux-store\rootReducer.ts 312 | force redux-store\rootSaga.ts 313 | force redux-store\storeState.ts 314 | ``` 315 | 316 | ### `yo nextjs-typescript-antd:component` 317 | 318 | It will prompt you the name for your new component. 319 | 320 | ``` 321 | $ yo nextjs-typescript-antd:component --force 322 | ? Component name UserDetails 323 | ? Is this a page-specific component? Yes 324 | ? Page name User 325 | create components\global\pages\user\userDetails\index.tsx 326 | create components\global\userDetails\styles.scss 327 | create static\locales\en\userDetails.json 328 | create tests\units\components\userDetails.test.js 329 | ``` 330 | Just as you can nest pages, you can also choose that your components be specific to pages which are nested. Below is the code sample: 331 | 332 | ``` 333 | $ yo nextjs-typescript-antd:component --force 334 | ? Component name user avatar 335 | ? Is this a page-specific component? Yes 336 | ? Page name 337 | about 338 | account 339 | > account/forgot-password 340 | index 341 | people 342 | (Move up and down to reveal more choices) 343 | ``` 344 | 345 | And the final output: 346 | ``` 347 | $ yo nextjs-typescript-antd:component --force 348 | ? Component name user avatar 349 | ? Is this a page-specific component? Yes 350 | ? Page name account/forgot-password 351 | create components\pages\account\forgot-password\userAvatar\index.tsx 352 | create components\pages\account\forgot-password\userAvatar\styles.scss 353 | force static\locales\en\userAvatar.json 354 | force tests\units\components\userAvatar.test.js 355 | force components\index.ts 356 | ``` 357 | 358 | Note that for the component name we entered `user avatar` and we got `userAvatar`. This is because all the component files should be `camelCase`d and the component names should be `PascalCase`d. Below is the generated component: 359 | 360 | ```jsx 361 | import React, {FC} from 'react'; 362 | import './styles.scss'; 363 | 364 | interface IProps {}; 365 | 366 | export const UserAvatar: FC = () => ( 367 |
368 | UserAvatar component 369 |
370 | ); 371 | 372 | export default UserAvatar; 373 | ``` 374 | 375 | 376 | And the `components\index.ts` file looks something like 377 | 378 | ```jsx 379 | export { default as CommentItem } from './global/commentItem'; 380 | export { default as CommentList } from './global/commentList'; 381 | export { default as CustomNProgress } from './global/customNProgress'; 382 | export { default as Layout } from './global/layout'; 383 | export { default as PostItem } from './global/postItem'; 384 | export { default as PostList } from './global/postList'; 385 | export { default as CustomErrorBoundary } from './global/customErrorBoundary'; 386 | export { default as UserAvatar } from './pages/account/forgot-password/userAvatar'; 387 | /* new-component-import-goes-here */ 388 | ``` 389 | This will allow you to import the `UserAvatar` component from anywhere in the project like below 390 | 391 | ```jsx 392 | ... 393 | import { UserAvatar } from 'components'; 394 | ... 395 | ``` 396 | 397 | This is possible because of the configuration in the `.babelrc` file. 398 | 399 | ```json 400 | ... 401 | "module-resolver", 402 | { 403 | "root": ["./"], 404 | "alias": { 405 | "api": "./api", 406 | "components": "./components", 407 | "constants": "./constants", 408 | "redux-store": "./redux-store" 409 | } 410 | } 411 | ... 412 | ``` 413 | ### `yo nextjs-typescript-antd:store` 414 | 415 | It will prompt you the name for your new store. 416 | 417 | ``` 418 | $ $ yo nextjs-typescript-antd:store --force 419 | ? Store name People 420 | create redux-store\people\actions.ts 421 | create redux-store\people\constants.ts 422 | create redux-store\people\reducer.ts 423 | create redux-store\people\sagas.ts 424 | create redux-store\people\selectors.ts 425 | create redux-store\people\state.ts 426 | force redux-store\rootReducer.ts 427 | force redux-store\rootSaga.ts 428 | force redux-store\storeState.ts 429 | ``` 430 | As you can see, there were 6 new files which were created. Let's have a look at each and explain what's happening in those. 431 | 432 | #### `state.ts` 433 | ```jsx 434 | export type PeopleErrable = 435 | | '__errable__' // Remove this. It's just a placeholder 436 | /* new-errable-goes-here */; 437 | 438 | export type PeopleBooleanable = 439 | | '__booleanable__' // Remove this. It's just a placeholder 440 | /* new-booleanable-goes-here */; 441 | 442 | export type PeopleSuccessible = 443 | | '__successible__' // Remove this. It's just a placeholder 444 | /* new-successible-goes-here */; 445 | 446 | export interface IPeopleState { 447 | //#region Doables 448 | readonly errable?: { [key in PeopleErrable]?: string }; 449 | readonly booleanable?: { [key in PeopleBooleanable]?: boolean }; 450 | readonly successible?: { [key in PeopleSuccessible]?: string }; 451 | //#endregion 452 | } 453 | ``` 454 | As you can see, we have 3 `type`s, namely `PeopleErrable`, `PeopleBooleanable` & `PeopleSuccessible` and they are initialized with default values which the comments state that they should be removed as soon as you start creating your own. 455 | 456 | The reason I introduced these 3 `type` in a newly-created store is to allow the user to define the state for all the actions that are `fullfillable` and by this, I simply mean the situation where an action can be dispatched and we wait for it finish (by either succeeding or failing). If we use fetching users as an example, we would normally have three properties in the state: `isFetchUsersInProgress`: `boolean`, `fetchUsersError`: `string` and (sometimes `fetchUserSuccess`: `string` - which, for the purpose of this example, would be a string like `"Successfully fetched the user!"`. 457 | 458 | This approach seem to work properly, except for when you have to create a selector to read them. You would have something like `selectisFetchUsersInProgress`, `fetchUserErrorMessage` and, finally, `fetchUserSuccess`. This creates a problem because for every `fullfilable` action, that would mean you would need a new selector. 459 | 460 | Another problem is when you need an action to set each of those. The old approach means you would need 3 actions, namely: `setisFetchUsersInProgress`, `setFetchUserErrorMessage` and `fetchUserSuccess`. That's not cool! So, to avoid having to do this, I just thought I could get away with these `doable` `type`s and define, in them, what can be doable. This allows me to create just one selector that allows me to select anything that can be `booleanable`, such as `isFetchUsersInProgress`, `isUpdateUserInProgress`, `isSigningOutInProgress`, `showDeleteModal`, etc. with just one selector like the one below 461 | 462 | ```jsx 463 | export const selectPeopleBooleanableState = (key: PeopleBooleanable | PeopleBooleanable[]) => 464 | 465 | createSelector( 466 | peopleState(), 467 | ({ booleanable }) => (Array.isArray(key) ? !!key.filter(k => booleanable[k]).length : booleanable[key]) 468 | ); 469 | ``` 470 | As you can see, I can simply do something like 471 | 472 | ```jsx 473 | const mapStateToProps = createStructuredSelector({ 474 | isFetchUsersInProgress= selectPeopleBooleanableState('isFetchUsersInProgress') 475 | isUpdateUserInProgress= selectPeopleBooleanableState('isUpdateUserInProgress') 476 | isSigningOutInProgress= selectPeopleBooleanableState('isSigningOutInProgress') 477 | showDeleteModal= selectPeopleBooleanableState('showDeleteModal') 478 | }); 479 | 480 | // Or the one that returns true if any of the above is true, like 481 | const mapStateToProps = createStructuredSelector({ 482 | showloader= selectPeopleBooleanableState(['isFetchUsersInProgress', 'isUpdateUserInProgress', 'isSigningOutInProgress']) 483 | }) 484 | ``` 485 | 486 | Please be sure to remove the default `doable`s like `__errable__`, `__booleanable__` & `__successible__` once you have added your own. 487 | 488 | Another example of creating an action that can set either of them, is in the `redux-store\people\actions.ts` 489 | 490 | ```jsx 491 | export const togglePeopleErrableState = createAction( 492 | TOGGLE_PEOPLE_ERRABLE_STATE, 493 | key => ({ 494 | errable: key, 495 | }) 496 | ); 497 | ``` 498 | 499 | This allows you to what is shown below. (This assumes you have `errable` states such as `fetcUserErrorMsg`, `updateUserErrorMsg`, `signInErrorMsg`, etc. 500 | 501 | ```jsx 502 | dispatch(togglePeopleErrableState({ fetcUserErrorMsg: '' )); 503 | dispatch(togglePeopleErrableState({ updateUserErrorMsg: 'Could not update the user!' )); 504 | dispatch(togglePeopleErrableState({ signInErrorMsg: '' )); 505 | 506 | // Or even 507 | dispatch(togglePeopleErrableState({ 508 | fetcUserErrorMsg: '', 509 | updateUserErrorMsg: 'Could not update the user!', 510 | signInErrorMsg: '' 511 | })); 512 | 513 | ``` 514 | 515 | All with just one action. 516 | 517 | ### `constants.ts` 518 | ```jsx 519 | export const DEFAULT_ACTION = 'DEFAULT_ACTION'; 520 | 521 | 522 | 523 | //#region Reset doable for this state 524 | export const RESET_PEOPLE_DOABLES = 'RESET_PEOPLE_DOABLES'; 525 | export const TOGGLE_PEOPLE_BOOLEANABLE_STATE = 'TOGGLE_PEOPLE_BOOLEANABLE_STATE'; 526 | export const TOGGLE_PEOPLE_ERRABLE_STATE = 'TOGGLE_PEOPLE_ERRABLE_STATE'; 527 | export const TOGGLE_PEOPLE_SUCCESSIBLE_STATE = 'TOGGLE_PEOPLE_SUCCESSIBLE_STATE'; 528 | //#endregion 529 | 530 | /* new-constant-export-goes-here */ 531 | ``` 532 | This contains the constants to set the `doable`s and reset them. Please do not remove the line that says `/* new-constant-export-goes-here */` as that is where the generated code will seat. 533 | 534 | #### `reducer.ts` 535 | 536 | ```jsx 537 | import { 538 | DEFAULT_ACTION, 539 | RESET_PEOPLE_DOABLES, 540 | /* new-constant-import-goes-here */ 541 | } from './constants'; 542 | 543 | import { IPeopleState } from './state'; 544 | import { reducerPayloadDoableHelper } from 'redux-store/rootReducer'; 545 | 546 | const initialState: IPeopleState = { 547 | errable: {}, 548 | booleanable: {}, 549 | successible: {}, 550 | }; 551 | 552 | export default ( 553 | state: IPeopleState = initialState, 554 | { type, payload: incomingPayload }: ReduxActions.Action 555 | ) => { 556 | const payload = 557 | type === RESET_PEOPLE_DOABLES 558 | ? incomingPayload 559 | : (reducerPayloadDoableHelper(state, incomingPayload) as IPeopleState); 560 | 561 | switch (type) { 562 | /* new-constant-cases-go-here */ 563 | case DEFAULT_ACTION: 564 | return { 565 | ...state, 566 | ...payload, 567 | } 568 | default: 569 | return state; 570 | } 571 | }; 572 | ``` 573 | Inside the reducer, the first line ensures that if the action being performed is `RESET_PEOPLE_DOABLES`, we make make use of `./rootReducer/reducerPayloadDoableHelper` which ensures that the current `errable`, `successible` and `boolean` are not overridden by the incoming values. A good example of when that can be the case is if our current state has something like this: 574 | 575 | ```jsx 576 | { 577 | booleanable: { 578 | isFetchUsersInProgress: true, 579 | isUpdateUserInProgress: false, 580 | } 581 | } 582 | ``` 583 | 584 | if we were to `togglePeopleBooleanableState` like below 585 | ```jsx 586 | dispatch(togglePeopleBooleanableState({ 587 | isSigningOutInProgress: false, showDeleteModal: false 588 | })); 589 | ``` 590 | 591 | this would have resulted in a new state being 592 | ```jsx 593 | { 594 | booleanable: { 595 | isSigningOutInProgress: false, 596 | showDeleteModal: false, 597 | } 598 | } 599 | ``` 600 | but what we are looking for is 601 | ```jsx 602 | { 603 | booleanable: { 604 | isFetchUsersInProgress: true, 605 | isUpdateUserInProgress: false, 606 | isSigningOutInProgress: false, 607 | showDeleteModal: false, 608 | } 609 | } 610 | ``` 611 | 612 | but the `reducerPayloadDoableHelper` function solves that problem. 613 | 614 | I will skip`sagas.ts` file. 615 | 616 | #### `selectors.ts` 617 | This is initialized with 3 selectors that are used to select the `errable`, `successible` or `booleanable` states of a given sub-state. and they are, in the case of `People` state, `selectPeopleBooleanableState`, `selectPeopleErrableState` and `selectPeopleSuccessibleState`. All these will be generated for you when you use the `:store` subgenerator. 618 | 619 | ### `yo nextjs-typescript-antd:action` 620 | 621 | It will prompt you the name for your new action. 622 | 623 | Below is an example of creating a `action`. The first question you are asked is t select the name of the state under which the action will be created. In our case, that will be a `People` state. 624 | ``` 625 | $ $ yo nextjs-typescript-antd:action --force 626 | ? Action name jump 627 | ? Select the store (Use arrow keys) 628 | > People 629 | Posts 630 | ``` 631 | 632 | In case you are wondering, the list of states is contained in the `.yo-rc.json` file and it's updated each time you use a generator to generate a state. Currently, it looks like below 633 | ```jsx 634 | { 635 | "generator-nextjs-typescript-antd": { 636 | "pages": [ 637 | ... 638 | ], 639 | "stores": [ 640 | "Posts", 641 | "People" 642 | ] 643 | } 644 | } 645 | ``` 646 | 647 | As you can see, there are two stores in our project. 648 | 649 | After that, you are asked if your action will can be fullfillable or not. An example of such can be one that triggers an API call, such as `fetchUsers` because an API can return either success or error in which case you might want to react to that by dispatching a success (`fetchUsersSuccess`) an error (`fetchUsersError`) action. 650 | ``` 651 | $ yo nextjs-typescript-antd:action --force 652 | ? Action name fetchUsers 653 | ? Select the store People 654 | ? Is it fullfillable? (Does it have SUCCESS & ERROR?) Yes 655 | force redux-store\people\constants.ts 656 | force redux-store\people\reducer.ts 657 | force redux-store\people\actions.ts 658 | force redux-store\people\state.ts 659 | force redux-store\people\sagas.ts 660 | ``` 661 | 662 | As you can see 5 files were updated. Let's see how they were all affected. 663 | 664 | ```jsx 665 | // redux-store\people\constants.ts 666 | 667 | ... 668 | //#region FETCH_USERS-related constants 669 | export const FETCH_USERS = 'FETCH_USERS'; 670 | export const FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS'; 671 | export const FETCH_USERS_ERROR = 'FETCH_USERS_ERROR'; 672 | //#endregion 673 | ... 674 | ``` 675 | 676 | ```jsx 677 | // redux-store\people\actions.ts 678 | 679 | ... 680 | //#region fetchUsers-related constants 681 | export const fetchUsers = createAction(FETCH_USERS, () => ({ 682 | booleanable: { isFetchUsersInProgress: true }, 683 | errable: { fetchUsersErrorMsg: null }, 684 | successible: { fetchUsersSuccessMsg: null }, 685 | })); 686 | 687 | export const fetchUsersSuccess = createAction(FETCH_USERS_SUCCESS, state => ({ 688 | booleanable: { isFetchUsersInProgress: false }, 689 | successible: { fetchUsersSuccessMsg: 'FETCH_USERS action fullfilled!' }, 690 | state, 691 | })); 692 | 693 | export const fetchUsersError = createAction(FETCH_USERS_ERROR, fetchUsersErrorMsg => ({ 694 | booleanable: { isFetchUsersInProgress: false }, 695 | errable: { fetchUsersErrorMsg }, 696 | })); 697 | //#endregion 698 | ... 699 | ``` 700 | 701 | ```jsx 702 | // redux-store\people\reducer.ts 703 | 704 | import { 705 | ... 706 | FETCH_USERS, 707 | FETCH_USERS_SUCCESS, 708 | FETCH_USERS_ERROR, 709 | /* new-constant-import-goes-here */ 710 | } from './constants'; 711 | 712 | ... 713 | 714 | export default ( 715 | state: IPeopleState = initialState, 716 | { type, payload: incomingPayload }: ReduxActions.Action 717 | ) => { 718 | const payload = ... 719 | 720 | switch (type) { 721 | ... 722 | case FETCH_USERS: 723 | case FETCH_USERS_SUCCESS: 724 | case FETCH_USERS_ERROR: 725 | /* new-constant-cases-go-here */ 726 | case DEFAULT_ACTION: 727 | return { 728 | ...state, 729 | ...payload, 730 | }; 731 | default: 732 | return state; 733 | } 734 | }; 735 | ``` 736 | 737 | ```jsx 738 | // redux-store\people\sagas.ts 739 | 740 | ... 741 | import { 742 | ... 743 | FETCH_USERS, 744 | /* new-constant-import-goes-here */ 745 | } from './constants'; 746 | import { 747 | ... 748 | fetchUsersSuccess, 749 | fetchUsersError, 750 | /* new-action-import-goes-here */ 751 | } from './actions'; 752 | ... 753 | function* fetchUsersSaga() { 754 | const BOOL_VALUE = Math.random() >= 0.5; 755 | 756 | yield delay(500); // Just sleep for half a sec just to look real. A saga requires a yield because it's a generator 757 | 758 | try { 759 | if (BOOL_VALUE) { 760 | yield put(fetchUsersSuccess({})); 761 | } else { 762 | yield put(fetchUsersError('Sorry, An error occured! Please try again later!')); 763 | } 764 | } catch (error) { 765 | yield put(fetchUsersError('Sorry, An error occured! Please try again later!')); 766 | } 767 | } 768 | 769 | export default function* peopleSaga() { 770 | yield all([ 771 | ... 772 | takeLatest(FETCH_USERS, fetchUsersSaga), 773 | /* new-saga-registration-goes-here */ 774 | ]); 775 | } 776 | 777 | ``` 778 | 779 | ```jsx 780 | // redux-store\people\state.ts 781 | ... 782 | export type PeopleErrable = 'fetchUsersErrorMsg' /* new-errable-goes-here */; 783 | export type PeopleBooleanable = 'isFetchUsersInProgress' /* new-booleanable-goes-here */; 784 | export type PeopleSuccessible = 'fetchUsersSuccessMsg' /* new-successible-goes-here */; 785 | ... 786 | ``` 787 | 788 | As you can see, it's too much code... but it's too much code that you don't get to write. 789 | 790 | In a case where you do not want to have your action that has `_ERROR` and `_SUCCESS`, you would have only three files updated such as below 791 | 792 | ```jsx 793 | $ yo nextjs-typescript-antd:action --force 794 | ? Action name jump 795 | ? Select the store People 796 | ? Is it fullfillable? (Does it have SUCCESS & ERROR?) No 797 | force redux-store\people\constants.ts 798 | force redux-store\people\reducer.ts 799 | force redux-store\people\actions.ts 800 | ``` 801 | 802 | And below is how each would have been affected 803 | ```jsx 804 | // redux-store\people\constants.ts 805 | ... 806 | //#region JUMP-related constants 807 | export const JUMP = 'JUMP'; 808 | //#endregion 809 | ... 810 | 811 | // redux-store\people\reducer.ts 812 | ... 813 | switch (type) { 814 | ... 815 | case JUMP: 816 | /* new-constant-cases-go-here */ 817 | case DEFAULT_ACTION: 818 | return { 819 | ...state, 820 | ...payload, 821 | }; 822 | default: 823 | return state; 824 | } 825 | 826 | ... 827 | // redux-store\people\actions.ts 828 | ... 829 | //#region jump-related constants 830 | export const jump = createAction(JUMP, state => state); // Make sure you pass a proper payload!!! 831 | //#endregion 832 | ... 833 | ``` 834 | 835 | As you can see, there was nothing updated with regards to `errable`, `successible` and `booleanable` states because the action is not `async` and can't fail. 836 | 837 | ### `yo nextjs-typescript-antd:enum` 838 | 839 | It will prompt you the name for your new enum. 840 | 841 | ``` 842 | $ yo nextjs-typescript-antd:enum --force 843 | ? Enum name UserRoles 844 | create enums\userRoles.ts 845 | force enums\index.ts 846 | ``` 847 | 848 | Below is how the two files will be affected 849 | 850 | ```jsx 851 | // enums\userRoles.ts 852 | export enum UserRoles { 853 | DefaultValue = 1, 854 | } 855 | ``` 856 | and 857 | ```jsx 858 | // enums\index.ts 859 | ... 860 | export { UserRoles } from './userRoles'; 861 | ... 862 | ``` 863 | ### `yo nextjs-typescript-antd:antdhoc` 864 | 865 | It will prompt you the name for your new higher-order component. 866 | 867 | ``` 868 | $ yo nextjs-typescript-antd:hoc --force 869 | ? HOC name withUserRoles 870 | create hocs\withUserRoles.tsx 871 | force hocs\index.ts 872 | ``` 873 | Below is how the two files will be affected 874 | 875 | ```jsx 876 | // hocs\withUserRoles.tsx 877 | 878 | import React, { FC, ComponentType } from 'react'; 879 | /** 880 | * 881 | * @param Component - the component that is passed into the HOC can be either a function component or class component. 882 | * 883 | * @see https://medium.com/@jrwebdev/react-higher-order-component-patterns-in-typescript-42278f7590fb 884 | */ 885 | const withUserRoles =

(Component: ComponentType

): FC

=> (props: P) => { 886 | // Your logic comes up in here 887 | return ; 888 | }; 889 | 890 | export default withUserRoles; 891 | ``` 892 | and 893 | ```jsx 894 | // hocs\index.ts 895 | ... 896 | export { default as withUserRoles } from './withUserRoles'; 897 | ... 898 | ``` 899 | 900 | ### `yo nextjs-typescript-antd:antdhook` 901 | 902 | It will prompt you the name for your new hook. 903 | 904 | ``` 905 | $ yo nextjs-typescript-antd:hook --force 906 | ? Hook name (Should start with use) useUserRoles 907 | create hooks\useUserRoles.ts 908 | force hooks\index.ts 909 | ``` 910 | Below is how the two files will be affected 911 | 912 | ```jsx 913 | // hooks\useUserRoles.ts 914 | 915 | export const useUserRoles = () => { 916 | // Put your logic here 917 | return null; 918 | }; 919 | ``` 920 | and 921 | ```jsx 922 | // hooks\index.ts 923 | ... 924 | export { useUserRoles } from './useUserRoles'; 925 | ... 926 | ``` 927 | 928 | ### `yo nextjs-typescript-antd:antdcontext` 929 | 930 | It will prompt you the name for your new context. 931 | 932 | ``` 933 | $ yo nextjs-typescript-antd:context --force 934 | ? Context name UserContext 935 | create contexts\userContext.ts 936 | force contexts\index.ts 937 | ``` 938 | Below is how the two files will be affected 939 | 940 | ```jsx 941 | // contexts\userContext.ts 942 | 943 | import { createContext } from 'react'; 944 | 945 | interface IUserContext {} 946 | 947 | export const defaultUserContext: IUserContext = {}; 948 | 949 | export const UserContext = createContext(defaultUserContext); 950 | ``` 951 | and 952 | ```jsx 953 | // contexts\index.ts 954 | ... 955 | export { UserContext } from './userContext'; 956 | ... 957 | ``` 958 | 959 | ### `yo nextjs-typescript-antd:model` 960 | 961 | It will prompt you the name for your new interface. 962 | 963 | ``` 964 | $ yo nextjs-typescript-antd:model --force 965 | ? Model name User 966 | create models\user.d.ts 967 | force models\index.d.ts 968 | ``` 969 | 970 | Below is how the two files will be affected 971 | 972 | ```jsx 973 | // models\user.d.ts 974 | 975 | export interface IUser { 976 | readonly id: string; 977 | } 978 | ``` 979 | and 980 | ```jsx 981 | // models\index.d.ts 982 | ... 983 | export { IUser } from './user'; 984 | ... 985 | ``` 986 | 987 | All that the subgenerators `:hoc`, `:hook`, `:model`, `:hook` and `:context` are giving you is a boilerplate and they also help enforcing the naming convention and eliminate the issues that can arise by someone creating something, like an `hoc` and they forget to export it in the `hocs/index.ts`. That will only mean that they can only import it as `import { someHoc } from 'hocs/someHoc';`. But they should only be importing it as `import { someHoc } from 'hocs';` and that is only possible if they don't forget to export it in the `hocs/index.ts` file. And, also, because the code is generated, you can not create a file in `PascalCase` , as in `SomeHoc` or even `hocs/some-hoc`. All file names will be in `camelCase`. 988 | 989 | # Changelog Generator 990 | Generate a changelog from git commits using https://github.com/lob/generate-changelog. This is meant to be used so that for every patch, minor, or major version, you update the changelog prior to running npm version so that the git tag contains the commit that updated both the changelog and version. 991 | 992 | # Release and Publish 993 | In order to release and publish we have created a npm script `npm run release-and-publish` that will create a new tag in github and will publish your pkg into npm. 994 | 995 | # What does this generator do? 996 | 997 | This yeoman generator will build different React components, creating a skeleton for the different files. 998 | 999 | ### TODO List 1000 | 1001 | - [ ] Allow The User To Choose A CSS Preprocessor (LESS OR LESS) 1002 | - [ ] Allow The User To Specify CRUD Requirements When Generating The Page 1003 | - [ ] Allow The User To Choose A [Layout](https://ant.design/components/layout/) When Generating The Boilerplate 1004 | 1005 | # Credits 1006 | 1007 | Electron Hacked 1008 | Github: https://github.com/ElectronHacked/nextjs-typescript-antd 1009 | 1010 | Inspired by https://github.com/AnalyticsFire/generator-create-next-app-reloaded by Damian Aruj 1011 | 1012 | # Licence 1013 | 1014 | MIT 1015 | -------------------------------------------------------------------------------- /generators/action/index.js: -------------------------------------------------------------------------------- 1 | const Generator = require('yeoman-generator'); 2 | const camelCase = require('camelcase'); 3 | const decamelize = require('decamelize'); 4 | 5 | module.exports = class extends Generator { 6 | prompting() { 7 | const stores = this.config.get('stores'); 8 | const storeOptions = stores.sort(); 9 | 10 | return this.prompt([ 11 | { 12 | type: 'input', 13 | name: 'name', 14 | message: 'Action name', 15 | validate: str => { 16 | if (str.trim().length > 0) { 17 | return true; 18 | } 19 | return 'Please add a name for your new action'; 20 | }, 21 | }, 22 | { 23 | type: 'list', 24 | name: 'reducerName', 25 | message: 'Select the store', 26 | choices: storeOptions, 27 | }, 28 | { 29 | type: 'confirm', 30 | name: 'isFulfillable', 31 | message: 'Is it fullfillable? (Does it have SUCCESS & ERROR?)', 32 | default: false, 33 | }, 34 | ]).then(({ name, reducerName, isFulfillable }) => { 35 | this.answers = { 36 | name: camelCase(name), 37 | reducerName, 38 | isFulfillable, 39 | }; 40 | }); 41 | } 42 | 43 | writing() { 44 | const { name, reducerName, isFulfillable } = this.answers; 45 | const actionNameToCamelCase = camelCase(name); 46 | const actionNameToPascalCase = camelCase(name, { 47 | pascalCase: true, 48 | }); 49 | 50 | const regionnify = (content, region) => `//#region ${region}\n${content}\n//#endregion\n`; 51 | 52 | const successToUpper = ACTION_NAME => `${ACTION_NAME}_SUCCESS`; 53 | const errorToUpper = ACTION_NAME => `${ACTION_NAME}_ERROR`; 54 | 55 | const successToCamelCase = ACTION_NAME => `${ACTION_NAME}Success`; 56 | const errorToCamelCase = ACTION_NAME => `${ACTION_NAME}Error`; 57 | 58 | const REDUX_STORE_BASE_PATH = `./redux-store/${reducerName.toLowerCase()}`; 59 | 60 | // #region Export from the `./actions` file 61 | 62 | const CONSTANT_NAME = decamelize(name, '_').toUpperCase(); 63 | 64 | let CONSTANT_EXPORT_STATEMENT = `export const ${CONSTANT_NAME} = '${CONSTANT_NAME}';`; 65 | 66 | let CONSTANTS_IMPORT_STATEMENTS = `${CONSTANT_NAME},`; 67 | 68 | const importConstantsHelper = path => { 69 | this.fs.copy(path, path, { 70 | process(content) { 71 | const regEx = new RegExp(/\/\* new-constant-import-goes-here \*\//, 'g'); 72 | const newContent = content 73 | .toString() 74 | .replace(regEx, `${CONSTANTS_IMPORT_STATEMENTS}\n\t/* new-constant-import-goes-here */`); 75 | return newContent; 76 | }, 77 | }); 78 | }; 79 | 80 | let CONSTANTS_CASE_STATEMENTS = `case ${CONSTANT_NAME}:`; 81 | 82 | if (isFulfillable) { 83 | CONSTANT_EXPORT_STATEMENT += `\nexport const ${successToUpper(CONSTANT_NAME)} = '${successToUpper( 84 | CONSTANT_NAME, 85 | )}';\nexport const ${errorToUpper(CONSTANT_NAME)} = '${errorToUpper(CONSTANT_NAME)}';`; 86 | 87 | CONSTANTS_IMPORT_STATEMENTS += `\n\t${successToUpper(CONSTANT_NAME)},\n\t${errorToUpper(CONSTANT_NAME)},`; 88 | 89 | CONSTANTS_CASE_STATEMENTS += `\n\t\tcase ${successToUpper(CONSTANT_NAME)}:\n\t\tcase ${errorToUpper( 90 | CONSTANT_NAME, 91 | )}:`; 92 | } 93 | 94 | // Export the constants 95 | const CONSTANTS_PATH = `${REDUX_STORE_BASE_PATH}/constants.ts`; 96 | 97 | // update constsntstss to export the newly-created actions 98 | this.fs.copy(CONSTANTS_PATH, CONSTANTS_PATH, { 99 | process(content) { 100 | const regEx = new RegExp(/\/\* new-constant-export-goes-here \*\//, 'g'); 101 | const newContent = content 102 | .toString() 103 | .replace( 104 | regEx, 105 | `${regionnify( 106 | CONSTANT_EXPORT_STATEMENT, 107 | `${CONSTANT_NAME}-related constants`, 108 | )}\n\n/* new-constant-export-goes-here */`, 109 | ); 110 | return newContent; 111 | }, 112 | }); 113 | // #endregion 114 | 115 | // #region Import actions in the reducer and put it in the case statements 116 | const REDUCER_PATH = `${REDUX_STORE_BASE_PATH}/reducer.ts`; 117 | 118 | // Import the constants 119 | importConstantsHelper(REDUCER_PATH); 120 | 121 | // Add constants in the case statemnts 122 | this.fs.copy(REDUCER_PATH, REDUCER_PATH, { 123 | process(content) { 124 | const regEx = new RegExp(/\/\* new-constant-cases-go-here \*\//, 'g'); 125 | const newContent = content 126 | .toString() 127 | .replace(regEx, `${CONSTANTS_CASE_STATEMENTS}\n\t\t/* new-constant-cases-go-here */`); 128 | return newContent; 129 | }, 130 | }); 131 | // #endregion 132 | 133 | // #region Actions file 134 | // Import the constants 135 | const ACTIONS_PATH = `${REDUX_STORE_BASE_PATH}/actions.ts`; 136 | 137 | // Import the constants 138 | importConstantsHelper(ACTIONS_PATH); 139 | 140 | let ACTIONS = ''; 141 | const booleanable = `is${actionNameToPascalCase}InProgress`; 142 | const errable = `${actionNameToCamelCase}ErrorMsg`; 143 | const successible = `${actionNameToCamelCase}SuccessMsg`; 144 | 145 | if (isFulfillable) { 146 | ACTIONS = ` 147 | export const ${actionNameToCamelCase} = createAction(${CONSTANT_NAME}, () => ({ 148 | booleanable: { ${booleanable}: true }, 149 | errable: { ${errable}: null }, 150 | successible: { ${successible}: null }, 151 | })); 152 | 153 | export const ${successToCamelCase( 154 | actionNameToCamelCase, 155 | )} = createAction(${successToUpper(CONSTANT_NAME)}, state => ({ 156 | booleanable: { ${booleanable}: false }, 157 | successible: { ${successible}: '${CONSTANT_NAME} action fullfilled!' }, 158 | state, 159 | })); 160 | 161 | export const ${errorToCamelCase( 162 | actionNameToCamelCase, 163 | )} = createAction(${errorToUpper(CONSTANT_NAME)}, ${errable} => ({ 164 | booleanable: { ${booleanable}: false }, 165 | errable: { ${errable} }, 166 | }));`; 167 | } else { 168 | ACTIONS = `export const ${actionNameToCamelCase} = createAction(${CONSTANT_NAME}, state => state); // Make sure you pass a proper payload!!!`; 169 | } 170 | 171 | // update constsntstss to export the newly-created actions 172 | this.fs.copy(ACTIONS_PATH, ACTIONS_PATH, { 173 | process(content) { 174 | const regEx = new RegExp(/\/\* new-actions-go-here \*\//, 'g'); 175 | const newContent = content 176 | .toString() 177 | .replace( 178 | regEx, 179 | `${regionnify(ACTIONS, `${actionNameToCamelCase}-related constants`)}\n\n/* new-actions-go-here */`, 180 | ); 181 | return newContent; 182 | }, 183 | }); 184 | // #endregion 185 | 186 | // #region Do the things in the state.ts 187 | if (isFulfillable) { 188 | const STATE_PATH = `${REDUX_STORE_BASE_PATH}/state.ts`; 189 | 190 | // Booleanable 191 | this.fs.copy(STATE_PATH, STATE_PATH, { 192 | process(content) { 193 | const regEx = new RegExp(/\/\* new-booleanable-goes-here \*\//, 'g'); 194 | const newContent = content.toString().replace(regEx, `| '${booleanable}' /* new-booleanable-goes-here */`); 195 | return newContent; 196 | }, 197 | }); 198 | 199 | // Errable 200 | this.fs.copy(STATE_PATH, STATE_PATH, { 201 | process(content) { 202 | const regEx = new RegExp(/\/\* new-errable-goes-here \*\//, 'g'); 203 | const newContent = content.toString().replace(regEx, `| '${errable}' /* new-errable-goes-here */`); 204 | return newContent; 205 | }, 206 | }); 207 | 208 | // Successible 209 | this.fs.copy(STATE_PATH, STATE_PATH, { 210 | process(content) { 211 | const regEx = new RegExp(/\/\* new-successible-goes-here \*\//, 'g'); 212 | const newContent = content.toString().replace(regEx, `| '${successible}' /* new-successible-goes-here */`); 213 | return newContent; 214 | }, 215 | }); 216 | } 217 | // #endregion 218 | 219 | // #region Sagas 220 | if (isFulfillable) { 221 | const SAGAS_PATH = `${REDUX_STORE_BASE_PATH}/sagas.ts`; 222 | 223 | // Import the constant 224 | this.fs.copy(SAGAS_PATH, SAGAS_PATH, { 225 | process(content) { 226 | const regEx = new RegExp(/\/\* new-constant-import-goes-here \*\//, 'g'); 227 | const newContent = content 228 | .toString() 229 | .replace(regEx, `${CONSTANT_NAME},\n\t/* new-constant-import-goes-here */`); 230 | return newContent; 231 | }, 232 | }); 233 | 234 | // Import the actions 235 | const ACTIONS_IMPORT_STATEMENTS = `${successToCamelCase(actionNameToCamelCase)},\n\t${errorToCamelCase( 236 | actionNameToCamelCase, 237 | )},`; 238 | 239 | this.fs.copy(SAGAS_PATH, SAGAS_PATH, { 240 | process(content) { 241 | const regEx = new RegExp(/\/\* new-action-import-goes-here \*\//, 'g'); 242 | const newContent = content 243 | .toString() 244 | .replace(regEx, `${ACTIONS_IMPORT_STATEMENTS}\n\t/* new-action-import-goes-here */`); 245 | return newContent; 246 | }, 247 | }); 248 | 249 | // Create a saga 250 | const ERROR_MSG = '"Sorry, An error occured! Please try again later!"'; 251 | const SAGA_NAME = `${actionNameToCamelCase}Saga`; 252 | 253 | const SAGA = ` 254 | function* ${SAGA_NAME}() { 255 | const BOOL_VALUE = Math.random() >= 0.5; 256 | 257 | yield delay(500); // Just sleep for half a sec just to look real. A saga requires a yield because it's a generator 258 | 259 | try { 260 | if (BOOL_VALUE) { 261 | yield put(${successToCamelCase(actionNameToCamelCase)}({})); 262 | } else { 263 | yield put(${errorToCamelCase(actionNameToCamelCase)}(${ERROR_MSG})); 264 | } 265 | } catch (error) { 266 | yield put(${errorToCamelCase(actionNameToCamelCase)}(${ERROR_MSG})); 267 | } 268 | } 269 | `; 270 | 271 | // Add a saga 272 | this.fs.copy(SAGAS_PATH, SAGAS_PATH, { 273 | process(content) { 274 | const regEx = new RegExp(/\/\* new-saga-goes-here \*\//, 'g'); 275 | const newContent = content.toString().replace(regEx, `${SAGA}\n\n/* new-saga-goes-here */`); 276 | return newContent; 277 | }, 278 | }); 279 | 280 | // Register the saga 281 | const SAGA_REGISTRATION = `takeLatest(${CONSTANT_NAME}, ${SAGA_NAME}),`; 282 | 283 | this.fs.copy(SAGAS_PATH, SAGAS_PATH, { 284 | process(content) { 285 | const regEx = new RegExp(/\/\* new-saga-registration-goes-here \*\//, 'g'); 286 | const newContent = content 287 | .toString() 288 | .replace(regEx, `${SAGA_REGISTRATION}\n\t\t/* new-saga-registration-goes-here */`); 289 | return newContent; 290 | }, 291 | }); 292 | // #endregion 293 | } 294 | // #endregion 295 | } 296 | }; 297 | -------------------------------------------------------------------------------- /generators/app/index.js: -------------------------------------------------------------------------------- 1 | const Generator = require('yeoman-generator'); 2 | const mkdirp = require('mkdirp'); 3 | const camelCase = require('camelcase'); 4 | const decamelize = require('decamelize'); 5 | 6 | module.exports = class extends Generator { 7 | // note: arguments and options should be defined in the constructor. 8 | constructor(args, opts) { 9 | super(args, opts); 10 | this.log('Initializing...'); 11 | 12 | // This makes `appname` a required argument. 13 | this.argument('appname', { 14 | type: String, 15 | required: false, 16 | }); 17 | } 18 | 19 | prompting() { 20 | return this.prompt([ 21 | { 22 | type: 'input', 23 | name: 'name', 24 | message: 'Your project name', 25 | default: this.options.appname || 'nextjs-ts-ant-app', 26 | }, 27 | { 28 | type: 'input', 29 | name: 'displayName', 30 | message: 'Your project display name', 31 | store: true, 32 | }, 33 | { 34 | type: 'input', 35 | name: 'fullname', 36 | message: "What's your full name", 37 | store: true, 38 | }, 39 | { 40 | type: 'input', 41 | name: 'email', 42 | message: "What's your email address", 43 | store: true, 44 | }, 45 | ]).then(({ name, displayName, fullName, email }) => { 46 | this.answers = { 47 | name: decamelize(camelCase(name), '-'), 48 | displayName, 49 | fullName, 50 | email, 51 | }; 52 | }); 53 | } 54 | 55 | writing() { 56 | const { name, fullName, email, displayName } = this.answers; 57 | 58 | // create folder project 59 | mkdirp(name); 60 | 61 | // change project root to the new folder 62 | this.destinationRoot(this.destinationPath(name)); 63 | 64 | // copy package.json and update some values 65 | this.fs.copyTpl(this.templatePath('_package.json'), this.destinationPath('package.json'), { 66 | name, 67 | fullName, 68 | email, 69 | }); 70 | 71 | // copy all files starting with .{whaetever} (like .eslintrc) 72 | this.fs.copy(this.templatePath('src/.*'), this.destinationPath('./')); 73 | 74 | // copy all folders and their contents 75 | this.fs.copy(this.templatePath('src'), this.destinationPath('./')); 76 | 77 | // Update the footer to have the name of the Application, the year and the user who created it 78 | try { 79 | this.fs.copyTpl(this.templatePath('_layout.tsx'), this.destinationPath('./components/global/layout/index.tsx'), { 80 | displayName, 81 | fullName, 82 | }); 83 | } catch (error) { 84 | if (error && error.error) { 85 | this.log(error.message); 86 | } 87 | } 88 | 89 | // Create the page 90 | this.config.set('pages', [ 91 | { 92 | name: 'about', 93 | path: 'about', 94 | }, 95 | { 96 | name: 'index', 97 | path: 'index', 98 | }, 99 | { 100 | name: 'post', 101 | path: 'post', 102 | }, 103 | { 104 | name: 'posts', 105 | path: 'posts', 106 | }, 107 | ]); 108 | 109 | // Create the page 110 | this.config.set('stores', ['Posts']); 111 | 112 | // save config file! 113 | this.config.save(); 114 | } 115 | 116 | install() { 117 | // install all dependencies 118 | this.npmInstall().then( 119 | () => { 120 | this.log('Dependencies Installed.'); 121 | }, 122 | () => this.log('We could not finish to install the node dependencies, please try again manually'), 123 | ); 124 | } 125 | 126 | end() { 127 | this.log(`Open your new project: cd ${this.answers.name}`); 128 | this.log('To run in dev mode use: yarn dev'); 129 | } 130 | }; 131 | -------------------------------------------------------------------------------- /generators/app/templates/_layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import uuid from 'uuid/v4'; 3 | import { Layout, Menu, Breadcrumb } from 'antd'; 4 | import Link from 'next/link'; 5 | import Head from './head'; 6 | // import {CustomNProgress} from 'components'; 7 | import { compose } from 'recompose'; 8 | import { withRouter, RouterProps } from 'next/router'; 9 | import '../../../styles/main.scss'; 10 | 11 | const { Header, Content, Footer } = Layout; 12 | const MenuItem = Menu.Item; 13 | 14 | interface Props extends React.HTMLAttributes { 15 | readonly children?: React.ReactNode; 16 | readonly description?: string; 17 | readonly ogImage?: string; 18 | readonly url?: string; 19 | readonly router?: RouterProps; 20 | } 21 | 22 | const activeClass = 'ant-menu-item-selected'; 23 | 24 | const MainLayout: React.SFC = ({ 25 | title, 26 | description, 27 | ogImage, 28 | url, 29 | router, 30 | children, 31 | }) => { 32 | const { asPath } = router; 33 | 34 | return ( 35 | <> 36 | {/* */} 37 | | ${title}`} description={description} ogImage={ogImage} url={url} /> 38 | 39 |

40 |
41 | 47 | 51 | 52 | Home 53 | 54 | 55 | 56 | 60 | 61 | About Us 62 | 63 | 64 | 65 | 69 | 70 | Posts 71 | 72 | 73 | 74 | {/* new-menu-item */} 75 | 76 |
77 | 78 | 79 | Home 80 | List 81 | App 82 | 83 |
84 | {children} 85 |
86 |
87 |
88 | <%= displayName %> @{new Date().getFullYear()} Created by <%= fullName %> 89 |
90 | 91 | 92 | ); 93 | } 94 | 95 | export default compose(withRouter)(MainLayout); 96 | -------------------------------------------------------------------------------- /generators/app/templates/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= name %>", 3 | "author": "<%= fullName %> <<%= email %>>", 4 | "version": "0.0.0", 5 | "license": "MIT", 6 | "scripts": { 7 | "dev": "node server.js", 8 | "export": "next export", 9 | "build": "next build", 10 | "start": "NODE_ENV=production node server.js", 11 | "test": "npm run test:units", 12 | "test:units": "NODE_ENV=testing jest --config tests/units/jest.config.js", 13 | "eslint-check": "eslint --print-config .eslintrc.js | eslint-config-prettier-check", 14 | "precommit": "lint-staged", 15 | "changelog:major": "changelog -M && git add CHANGELOG.md && git commit -m 'updated CHANGELOG.md' && npm version major && git push origin", 16 | "changelog:minor": "changelog -m && git add CHANGELOG.md && git commit -m 'updated CHANGELOG.md' && npm version minor && git push origin", 17 | "changelog:patch": "changelog -p && git add CHANGELOG.md && git commit -m 'updated CHANGELOG.md' && npm version patch && git push origin", 18 | "release": "git push origin --tags", 19 | "release-and-publish": "npm run release && npm publish" 20 | }, 21 | "lint-staged": { 22 | "*.{js,jsx,json,css}": ["prettier --single-quote --write", "git add"] 23 | }, 24 | "dependencies": { 25 | "@types/cheerio": "^0.22.11", 26 | "@types/react-input-mask": "^2.0.1", 27 | "@zeit/next-bundle-analyzer": "^0.1.2", 28 | "@zeit/next-css": "^1.0.1", 29 | "@zeit/next-less": "^1.0.1", 30 | "@zeit/next-sass": "^1.0.1", 31 | "@zeit/next-source-maps": "0.0.3", 32 | "@zeit/next-typescript": "^1.1.1", 33 | "acorn": "^6.1.1", 34 | "antd": "^3.19.6", 35 | "axios": "^0.18.0", 36 | "babel-cli": "^6.26.0", 37 | "babel-core": "^6.26.3", 38 | "babel-eslint": "^10.0.1", 39 | "babel-jest": "^24.1.0", 40 | "babel-plugin-import": "^1.11.0", 41 | "babel-plugin-module-resolver": "^3.1.3", 42 | "babel-preset-env": "^1.7.0", 43 | "babel-preset-next": "^1.2.0", 44 | "babel-preset-react": "^6.24.1", 45 | "camel-case": "^3.0.0", 46 | "cheerio": "^1.0.0-rc.3", 47 | "color-convert": "^2.0.0", 48 | "compression": "^1.7.3", 49 | "config": "^3.0.1", 50 | "core-js": "^3.1.3", 51 | "diff-dates": "^1.0.12", 52 | "dotenv": "^6.2.0", 53 | "duration": "^0.2.2", 54 | "enzyme": "^3.8.0", 55 | "enzyme-adapter-react-16": "^1.9.1", 56 | "enzyme-to-json": "^3.3.5", 57 | "express": "^4.16.4", 58 | "faker": "^4.1.0", 59 | "filter-chunk-webpack-plugin": "^2.1.0", 60 | "generate-changelog": "^1.7.1", 61 | "glob": "^7.1.4", 62 | "htmlescape": "^1.1.1", 63 | "i18next": "^15.0.0", 64 | "i18next-browser-languagedetector": "^3.0.0", 65 | "i18next-express-middleware": "^1.7.1", 66 | "i18next-node-fs-backend": "^2.1.1", 67 | "i18next-xhr-backend": "^2.0.0", 68 | "interactjs": "^1.4.0", 69 | "jest": "^24.1.0", 70 | "jest-plugin-context": "^2.9.0", 71 | "less": "^3.9.0", 72 | "less-vars-to-js": "^1.3.0", 73 | "lodash": "^4.17.11", 74 | "next": "^8.1.0", 75 | "next-compose-plugins": "^2.1.1", 76 | "next-images": "^1.1.1", 77 | "next-redux-saga": "^3.0.0", 78 | "next-redux-wrapper": "^3.0.0-alpha.0", 79 | "next-runtime-dotenv": "^1.0.1", 80 | "npm": "^6.9.0", 81 | "nprogress": "^0.2.0", 82 | "optimize-css-assets-webpack-plugin": "^5.0.1", 83 | "path": "^0.12.7", 84 | "prettier": "^1.16.4", 85 | "prettier-eslint-cli": "^4.7.1", 86 | "qs": "^6.7.0", 87 | "randomcolor": "^0.5.4", 88 | "react": "^16.8.6", 89 | "react-dom": "^16.8.1", 90 | "react-error-boundary": "^1.2.5", 91 | "react-i18next": "^10.0.4", 92 | "react-input-mask": "^2.0.4", 93 | "react-lines-ellipsis": "^0.14.0", 94 | "react-loading": "^2.0.3", 95 | "react-redux": "^6.0.0", 96 | "react-resize-detector": "^4.1.3", 97 | "react-spinners": "^0.5.4", 98 | "react-table": "^6.10.0", 99 | "react-test-renderer": "^16.8.1", 100 | "react-use": "^7.3.1", 101 | "react-use-form-state": "^0.8.0", 102 | "react-web-notification": "^0.5.0", 103 | "recompose": "^0.30.0", 104 | "redux": "^4.0.1", 105 | "redux-devtools-extension": "^2.13.8", 106 | "redux-logger": "^3.0.6", 107 | "redux-persist": "^5.10.0", 108 | "redux-react-hook": "^3.3.1", 109 | "redux-saga": "^1.0.3", 110 | "utility-types": "^3.5.0", 111 | "uuid": "^3.3.2" 112 | }, 113 | "devDependencies": { 114 | "@babel/cli": "^7.4.4", 115 | "@babel/core": "^7.2.2", 116 | "@types/color-convert": "^1.9.0", 117 | "@types/config": "0.0.34", 118 | "@types/faker": "^4.1.5", 119 | "@types/lodash": "^4.14.136", 120 | "@types/next": "^8.0.5", 121 | "@types/next-redux-wrapper": "^2.0.2", 122 | "@types/node": "^12.0.10", 123 | "@types/qs": "^6.5.3", 124 | "@types/react": "^16.8.2", 125 | "@types/react-dom": "^16.8.0", 126 | "@types/react-redux": "^7.0.1", 127 | "@types/react-table": "6.7.10", 128 | "@types/recompose": "^0.30.3", 129 | "@types/redux-actions": "^2.3.1", 130 | "@types/uuid": "^3.4.4", 131 | "@types/webpack-env": "^1.13.9", 132 | "babel-loader": "^8.0.6", 133 | "babel-preset-react-app": "^9.0.0", 134 | "css-loader": "^1.0.1", 135 | "eslint": "^5.13.0", 136 | "eslint-config-airbnb": "^17.1.0", 137 | "eslint-config-prettier": "^4.0.0", 138 | "eslint-config-standard": "^12.0.0", 139 | "eslint-import-resolver-babel-module": "^5.0.1", 140 | "eslint-plugin-import": "^2.17.3", 141 | "eslint-plugin-jsx-a11y": "^6.2.1", 142 | "eslint-plugin-node": "^8.0.1", 143 | "eslint-plugin-prettier": "^3.0.1", 144 | "eslint-plugin-promise": "^4.0.1", 145 | "eslint-plugin-react": "^7.12.4", 146 | "eslint-plugin-react-hooks": "^1.4.0", 147 | "eslint-plugin-standard": "^4.0.0", 148 | "fork-ts-checker-webpack-plugin": "^0.5.2", 149 | "husky": "^1.3.1", 150 | "i": "^0.3.6", 151 | "less-loader": "^5.0.0", 152 | "lint-staged": "^8.1.3", 153 | "mini-css-extract-plugin": "^0.5.0", 154 | "node-sass": "^4.12.0", 155 | "postcss-loader": "^3.0.0", 156 | "redux-actions": "^2.6.5", 157 | "reselect": "^4.0.0", 158 | "sass-loader": "^7.1.0", 159 | "style-loader": "^0.23.1", 160 | "tslint": "^5.12.1", 161 | "tslint-config-prettier": "^1.18.0", 162 | "tslint-config-standard": "^8.0.1", 163 | "tslint-loader": "^3.5.4", 164 | "tslint-react": "^3.6.0", 165 | "typescript": "^3.4.5", 166 | "typescriptnpm": "^1.0.1" 167 | } 168 | } -------------------------------------------------------------------------------- /generators/app/templates/src/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel", "@zeit/next-typescript/babel"], 3 | "plugins": [ 4 | [ 5 | "module-resolver", 6 | { 7 | "root": ["./"], 8 | "alias": { 9 | "api": "./api", 10 | "app-constants": "./app-constants", 11 | "enums": "./enums", 12 | "hocs": "./hocs", 13 | "hooks": "./hooks", 14 | "components": "./components", 15 | "redux-store": "./redux-store" 16 | } 17 | } 18 | ], 19 | [ 20 | "import", 21 | { 22 | "libraryName": "antd", 23 | "style": true 24 | } 25 | ] 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /generators/app/templates/src/.eslintignore: -------------------------------------------------------------------------------- 1 | /.next/** 2 | /.out/** 3 | /node_modules/** 4 | -------------------------------------------------------------------------------- /generators/app/templates/src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "standard", 4 | "plugin:react/recommended", 5 | "prettier", 6 | "prettier/react", 7 | "prettier/standard" 8 | ], 9 | "plugins": [ 10 | "react", 11 | "prettier", 12 | "standard" 13 | ], 14 | "parserOptions": { 15 | "sourceType": "module", 16 | "ecmaFeatures": { 17 | "jsx": true 18 | } 19 | }, 20 | "env": { 21 | "es6": true, 22 | "node": true 23 | }, 24 | "rules": { 25 | "prettier/prettier": ["error", { "singleQuote": true }] 26 | } 27 | } -------------------------------------------------------------------------------- /generators/app/templates/src/.gitattributes: -------------------------------------------------------------------------------- 1 | # From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes 2 | 3 | # Handle line endings automatically for files detected as text 4 | # and leave all files detected as binary untouched. 5 | * text=auto 6 | 7 | # 8 | # The above will handle all files NOT found below 9 | # 10 | 11 | # 12 | ## These files are text and should be normalized (Convert crlf => lf) 13 | # 14 | 15 | # source code 16 | *.php text 17 | *.css text 18 | *.sass text 19 | *.scss text 20 | *.less text 21 | *.styl text 22 | *.js text eol=lf 23 | *.coffee text 24 | *.json text 25 | *.htm text 26 | *.html text 27 | *.xml text 28 | *.svg text 29 | *.txt text 30 | *.ini text 31 | *.inc text 32 | *.pl text 33 | *.rb text 34 | *.py text 35 | *.scm text 36 | *.sql text 37 | *.sh text 38 | *.bat text 39 | 40 | # templates 41 | *.ejs text 42 | *.hbt text 43 | *.jade text 44 | *.haml text 45 | *.hbs text 46 | *.dot text 47 | *.tmpl text 48 | *.phtml text 49 | 50 | # server config 51 | .htaccess text 52 | .nginx.conf text 53 | 54 | # git config 55 | .gitattributes text 56 | .gitignore text 57 | .gitconfig text 58 | 59 | # code analysis config 60 | .jshintrc text 61 | .jscsrc text 62 | .jshintignore text 63 | .csslintrc text 64 | 65 | # misc config 66 | *.yaml text 67 | *.yml text 68 | .editorconfig text 69 | 70 | # build config 71 | *.npmignore text 72 | *.bowerrc text 73 | 74 | # Heroku 75 | Procfile text 76 | .slugignore text 77 | 78 | # Documentation 79 | *.md text 80 | LICENSE text 81 | AUTHORS text 82 | 83 | 84 | # 85 | ## These files are binary and should be left untouched 86 | # 87 | 88 | # (binary is a macro for -text -diff) 89 | *.png binary 90 | *.jpg binary 91 | *.jpeg binary 92 | *.gif binary 93 | *.ico binary 94 | *.mov binary 95 | *.mp4 binary 96 | *.mp3 binary 97 | *.flv binary 98 | *.fla binary 99 | *.swf binary 100 | *.gz binary 101 | *.zip binary 102 | *.7z binary 103 | *.ttf binary 104 | *.eot binary 105 | *.woff binary 106 | *.pyc binary 107 | *.pdf binary 108 | -------------------------------------------------------------------------------- /generators/app/templates/src/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | /dist 12 | /.next 13 | /.out 14 | 15 | # misc 16 | .DS_Store 17 | .env 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | 22 | # Don't add any ide-related generated files 23 | .vscode 24 | .sonarlint -------------------------------------------------------------------------------- /generators/app/templates/src/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "tabWidth": 2, 4 | "trailingComma": "es5", 5 | "printWidth": 120, 6 | "bracketSpacing": true 7 | } 8 | -------------------------------------------------------------------------------- /generators/app/templates/src/LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Analytics Fire 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. -------------------------------------------------------------------------------- /generators/app/templates/src/README.md: -------------------------------------------------------------------------------- 1 | # A great component builder for ReactJs using NextJs 2 | 3 | This yeoman generator will build different React components, creating a skeleton for the different files. 4 | 5 | # Requirements 6 | 7 | This is a Yeoman generator. You need to install Yeoman, NodeJS and npm to install the generator and its dependencies. Make sure you have all installed globally. 8 | 9 | First, download and install NodeJS and npm. More information about NodeJS / npm: https://nodejs.org/ 10 | 11 | Second, install Yeoman. More information about Yeoman: http://yeoman.io/ 12 | 13 | # Installation 14 | 15 | ``` 16 | $ npm install -g generator-create-next-app-reloaded 17 | ``` 18 | 19 | # Usage 20 | 21 | ``` 22 | $ yo create-next-app-reloaded 23 | $ cd create-next-app-reloaded 24 | $ yarn dev 25 | ``` 26 | 27 | # Table of Contents 28 | 29 | * [Questions? Feedback?](#questions-feedback) 30 | * [Folder Structure](#folder-structure) 31 | * [Available Scripts](#available-scripts) 32 | * [yarn dev](#yarn-dev) 33 | * [yarn build](#yarn-build) 34 | * [yarn start](#yarn-start) 35 | * [Available Generators](#available-generators) 36 | * [yo create-next-app-reloaded:page](#yo-create-next-app-reloadedpage) 37 | * [yo create-next-app-reloaded:component](#yo-create-next-app-reloadedcomponent) 38 | * [Changelog](#changelog-generator) 39 | * [Release and Publish](#release-and-publish) 40 | 41 | # Questions? Feedback? 42 | 43 | Check out [Next.js FAQ & docs](https://github.com/zeit/next.js#faq) or [let us know](https://github.com/segmentio/create-next-app/issues) your feedback. 44 | 45 | # Folder Structure 46 | 47 | After creating an app, it should look something like: 48 | 49 | ``` 50 | my-app/ 51 | README.md 52 | .gitignore 53 | .prettierrc 54 | i18n.js 55 | .babelrc 56 | .eslintrc 57 | static/ 58 | favicon.ico 59 | locales/ 60 | en/ 61 | common.js 62 | images/ 63 | tests/ 64 | units/ 65 | components/ 66 | jest.config.js 67 | pages/ 68 | setup/ 69 | index.js 70 | assetsTransformer.js 71 | server.js 72 | components/ 73 | activeLink/ 74 | footer/ 75 | header/ 76 | layout/ 77 | navLink/ 78 | characterInfo/ 79 | nav/ 80 | config/ 81 | custom-environment-variables.js 82 | default.js 83 | development.js 84 | production.js 85 | lib/ 86 | withI18next.js 87 | config.shim.js 88 | next.config.js 89 | pages/ 90 | about/ 91 | index.js 92 | _app.js 93 | _document.js 94 | home/ 95 | redux-example/ 96 | styles/ 97 | _mixins.scss 98 | _variables.scss/ 99 | main.scss 100 | vendors/ 101 | bootstrap-4.0.0-beta.2/ 102 | redux/ 103 | actions.js 104 | epics.js 105 | index.js 106 | reducer.js 107 | package.json 108 | ``` 109 | 110 | # Available Scripts 111 | 112 | ### `yarn dev` 113 | 114 | Runs the app in the development mode.
115 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 116 | 117 | The page will reload if you make edits.
118 | You will also see any errors in the console. 119 | 120 | ### `yarn build` 121 | 122 | Builds the app for production to the `.next` folder.
123 | It correctly bundles React in production mode and optimizes the build for the best performance. 124 | 125 | ### `yarn start` 126 | 127 | Starts the application in production mode. 128 | The application should be compiled with \`next build\` first. 129 | 130 | See the section in Next docs about [deployment](https://github.com/zeit/next.js/wiki/Deployment) for more information. 131 | 132 | # Available Generators 133 | 134 | ### `yo create-next-app-reloaded:page` 135 | 136 | It will prompt you the name and the title for your new page. 137 | 138 | ``` 139 | $ yo create-next-app-reloaded:page 140 | ? Page name: contactUs 141 | ? Page title: Contact Us Page 142 | create pages/contactUs/contactUs.js 143 | create pages/contactUs/index.js 144 | create pages/contactUs/contactUs.scss 145 | create static/locales/en/contactUs.json 146 | create tests/units/pages/contactUs.test.js 147 | conflict server.js // This is because we are adding the new i18n namespace into the namespaces array. 148 | ? Overwrite server.js? overwrite 149 | force server.js 150 | ``` 151 | 152 | ### `yo create-next-app-reloaded:component` 153 | 154 | It will prompt you the name for your new component. 155 | 156 | ``` 157 | $ yo create-next-app-reloaded:component 158 | ? Component name: myNav 159 | create components/myNav/myNav.js 160 | create components/myNav/index.js 161 | create components/myNav/casa.scss 162 | create static/locales/en/myNav.json 163 | create tests/units/components/myNav.test.js 164 | ``` 165 | 166 | # Changelog Generator 167 | Generate a changelog from git commits using https://github.com/lob/generate-changelog. This is meant to be used so that for every patch, minor, or major version, you update the changelog prior to running npm version so that the git tag contains the commit that updated both the changelog and version. 168 | 169 | # Release and Publish 170 | In order to release and publish we have created a npm script `npm run release-and-publish` that will create a new tag in github and will publish your pkg into npm. 171 | 172 | # What does this generator do? 173 | 174 | This yeoman generator will build different React components, creating a skeleton for the different files. 175 | 176 | # Credits 177 | 178 | Damian Aruj 179 | Github: https://github.com/analyticsfire/generator-create-next-app-reloaded 180 | 181 | # Licence 182 | 183 | MIT 184 | -------------------------------------------------------------------------------- /generators/app/templates/src/api/postsApi.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const BASE_URL = 'https://jsonplaceholder.typicode.com'; 4 | 5 | export const fetchAllPostsApi = () => 6 | axios 7 | .get(`${BASE_URL}/posts`) 8 | .then(response => response) 9 | .catch(error => error.response); 10 | 11 | export const fetchPostCommentsApi = postId => 12 | axios 13 | .get(`${BASE_URL}/posts/${postId}/comments`) 14 | .then(response => response) 15 | .catch(error => error.response); 16 | -------------------------------------------------------------------------------- /generators/app/templates/src/app-constants/index.ts: -------------------------------------------------------------------------------- 1 | /* new-constant-export-goes-here */ 2 | -------------------------------------------------------------------------------- /generators/app/templates/src/components/global/commentItem/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Card } from 'antd'; 3 | import { IComment } from 'models'; 4 | import './styles.scss'; 5 | 6 | interface IProps { 7 | readonly comment: IComment; 8 | } 9 | 10 | const CommentItem: React.SFC = ({ comment }) => { 11 | const { name, body } = comment; 12 | 13 | return ( 14 | 15 |
16 |

{body}

17 |

{name}

18 |
19 |
20 | ); 21 | }; 22 | 23 | export default CommentItem; 24 | -------------------------------------------------------------------------------- /generators/app/templates/src/components/global/commentItem/styles.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronHacked/nextjs-typescript-antd/48476fd223bd14e408f1b82025643aea8f724236/generators/app/templates/src/components/global/commentItem/styles.scss -------------------------------------------------------------------------------- /generators/app/templates/src/components/global/commentList/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import uuid from 'uuid/v4'; 3 | import { createSelector } from 'reselect'; 4 | import { compose } from 'recompose'; 5 | import { connect } from 'react-redux'; 6 | import { IComment } from 'models'; 7 | import { selectSelectedPostComments } from 'redux-store/posts/selectors'; 8 | import './styles.scss'; 9 | import CommentItem from './../commentItem'; 10 | 11 | interface IProps { 12 | readonly comments: IComment[]; 13 | } 14 | 15 | const CommentList: React.SFC = ({ comments }) => { 16 | return ( 17 | 18 | {comments.length > 0 && ( 19 | 20 |

Comments

21 | {comments.map(comment => ( 22 | 23 | ))} 24 |
25 | )} 26 |
27 | ); 28 | }; 29 | 30 | const mapStateToProps = createSelector( 31 | selectSelectedPostComments(), 32 | comments => ({ comments }) 33 | ); 34 | 35 | const withConnect = connect(mapStateToProps); 36 | 37 | export default compose(withConnect)(CommentList); 38 | -------------------------------------------------------------------------------- /generators/app/templates/src/components/global/commentList/styles.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronHacked/nextjs-typescript-antd/48476fd223bd14e408f1b82025643aea8f724236/generators/app/templates/src/components/global/commentList/styles.scss -------------------------------------------------------------------------------- /generators/app/templates/src/components/global/customErrorBoundary/fallbackComponent.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { FallbackProps } from 'react-error-boundary'; 3 | import { Icon, Button } from 'antd'; 4 | import Router from 'next/router'; 5 | import './styles.scss'; 6 | 7 | const errorBoundaryErrorHandler = ({ error, componentStack }: FallbackProps) => { 8 | // Do something with the error 9 | // E.g. log to an error logging client here 10 | console.log('CustomErrorBoundary error :', error.message); 11 | console.log('CustomErrorBoundary componentStack :', componentStack); 12 | }; 13 | 14 | const CustomErrorBoundaryFallbackComponent: FC = props => { 15 | errorBoundaryErrorHandler(props); 16 | 17 | return ( 18 |
19 |

Oops!

20 | 21 |

Aaaah! Something went wrong!

22 |

23 | Brace yourself till we get the error fixed. You may also refresh the page or try again later 24 |

25 | 26 | 29 |
30 | ); 31 | }; 32 | 33 | export default CustomErrorBoundaryFallbackComponent; 34 | -------------------------------------------------------------------------------- /generators/app/templates/src/components/global/customErrorBoundary/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, ReactNode } from 'react'; 2 | import ErrorBoundary from 'react-error-boundary'; 3 | import CustomErrorBoundaryFallbackComponent from './fallbackComponent'; 4 | import './styles.scss'; 5 | 6 | interface IProps { 7 | children: ReactNode; 8 | } 9 | 10 | export const CustomErrorBoundary: FC = ({ children }) => { 11 | return {children}; 12 | }; 13 | 14 | export default CustomErrorBoundary; 15 | -------------------------------------------------------------------------------- /generators/app/templates/src/components/global/customErrorBoundary/styles.scss: -------------------------------------------------------------------------------- 1 | $big-font: 45px; 2 | $primary-font: 26px; 3 | $secondary-font: 18px; 4 | 5 | .custom-error-boundary { 6 | display: flex; 7 | flex: 1; 8 | flex-direction: column; 9 | justify-content: center; 10 | align-items: center; 11 | justify-items: center; 12 | align-content: center; 13 | 14 | 15 | .oops { 16 | font-size: $big-font; 17 | } 18 | 19 | .error-icon { 20 | margin-top: 15px; 21 | font-size: $big-font; 22 | } 23 | 24 | .primary-message { 25 | font-size: $primary-font; 26 | } 27 | 28 | .secondary-message { 29 | font-size: $secondary-font; 30 | } 31 | 32 | .take-me-home { 33 | margin-bottom: $big-font; 34 | } 35 | } -------------------------------------------------------------------------------- /generators/app/templates/src/components/global/customnprogress/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Head from 'next/head'; 3 | import NProgress from 'nprogress'; 4 | import Router from 'next/router'; 5 | 6 | NProgress.configure({ showSpinner: false }); 7 | 8 | Router.onRouteChangeStart = _ => { 9 | NProgress.start(); 10 | }; 11 | 12 | Router.onRouteChangeComplete = () => NProgress.done(); 13 | Router.onRouteChangeError = () => NProgress.done(); 14 | 15 | export default () => ( 16 |
17 | 18 | {/* Import CSS for nprogress */} 19 | 20 | 21 |
22 | ); 23 | -------------------------------------------------------------------------------- /generators/app/templates/src/components/global/layout/head.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import NextHead from 'next/head'; 3 | 4 | const defaultDescription = ''; 5 | const defaultOGURL = ''; 6 | const defaultOGImage = ''; 7 | 8 | interface Props { 9 | readonly title: string; 10 | readonly description?: string; 11 | readonly url?: string; 12 | readonly ogImage?: string; 13 | } 14 | 15 | const Head: React.SFC = ({title, description, url, ogImage}) => 16 | 17 | 18 | 19 | {title || ''} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ; 37 | 38 | export default Head; 39 | -------------------------------------------------------------------------------- /generators/app/templates/src/components/global/postItem/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { IPost } from 'models'; 3 | import { Card } from 'antd'; 4 | import Link from 'next/link'; 5 | import './styles.scss'; 6 | 7 | export interface IProps { 8 | readonly post: IPost; 9 | readonly hasViewCommentsLink?: boolean; 10 | } 11 | 12 | const PostItem: React.SFC = ({ post, hasViewCommentsLink = true }) => { 13 | const { id, title, body } = post; 14 | 15 | return ( 16 | 22 | View Comments 23 | 24 | ) 25 | } 26 | > 27 |

{body}

28 |
29 | ); 30 | }; 31 | 32 | export default PostItem; 33 | -------------------------------------------------------------------------------- /generators/app/templates/src/components/global/postItem/styles.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronHacked/nextjs-typescript-antd/48476fd223bd14e408f1b82025643aea8f724236/generators/app/templates/src/components/global/postItem/styles.scss -------------------------------------------------------------------------------- /generators/app/templates/src/components/global/postList/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import uuid from 'uuid/v4'; 3 | import PostItem from '../postItem'; 4 | import { createSelector } from 'reselect'; 5 | import { selectAllPosts } from 'redux-store/posts/selectors'; 6 | import { compose } from 'recompose'; 7 | import { connect } from 'react-redux'; 8 | import { IPost } from 'models'; 9 | import './styles.scss'; 10 | 11 | interface IProps { 12 | readonly posts: IPost[]; 13 | } 14 | 15 | const PostList: React.SFC = ({ posts }) => ( 16 | 17 |

Posts

18 | {posts.map(post => ( 19 | 20 | ))} 21 |
22 | ); 23 | 24 | const mapStateToProps = createSelector( 25 | selectAllPosts(), 26 | posts => ({ posts }) 27 | ); 28 | 29 | const withConnect = connect(mapStateToProps); 30 | 31 | export default compose(withConnect)(PostList); 32 | -------------------------------------------------------------------------------- /generators/app/templates/src/components/global/postList/styles.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronHacked/nextjs-typescript-antd/48476fd223bd14e408f1b82025643aea8f724236/generators/app/templates/src/components/global/postList/styles.scss -------------------------------------------------------------------------------- /generators/app/templates/src/components/global/snazzyButton/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import './styles.scss'; 3 | 4 | interface IProps extends React.CSSProperties { 5 | /** 6 | * The text of the button 7 | */ 8 | text?: string; 9 | disabled?: boolean; 10 | onClick?: () => any; 11 | } 12 | 13 | const SnazzyButton: React.FC = ({ text, disabled = false, ...styles }) => { 14 | return ( 15 | 18 | ); 19 | }; 20 | 21 | SnazzyButton.displayName = 'SnazzyButton'; 22 | 23 | export default SnazzyButton; 24 | -------------------------------------------------------------------------------- /generators/app/templates/src/components/global/snazzyButton/snazzyButton.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import SnazzyButton from './index'; 4 | import { text, color, boolean, number } from '@storybook/addon-knobs/react'; 5 | 6 | storiesOf('Components|SnazzyButton', module).addWithJSX( 7 | 'with knobs', 8 | () => { 9 | return ( 10 | 18 | ); 19 | }, 20 | // tslint:disable-next-line:trailing-comma 21 | { notes: 'A very simple component', info: { inline: true } } 22 | ); 23 | -------------------------------------------------------------------------------- /generators/app/templates/src/components/global/snazzyButton/styles.scss: -------------------------------------------------------------------------------- 1 | .sha-btn { 2 | -moz-box-shadow: inset 0px 1px 0px 0px #fce2c1; 3 | -webkit-box-shadow: inset 0px 1px 0px 0px #fce2c1; 4 | box-shadow: inset 0px 1px 0px 0px #fce2c1; 5 | background-color: #ffc477; 6 | -moz-border-radius: 6px; 7 | -webkit-border-radius: 6px; 8 | border-radius: 6px; 9 | border: 1px solid #eeb44f; 10 | display: inline-block; 11 | cursor: pointer; 12 | color: #ffffff; 13 | font-family: Arial; 14 | font-size: 15px; 15 | font-weight: bold; 16 | padding: 6px 24px; 17 | text-decoration: none; 18 | text-shadow: 0px 1px 0px #cc9f52; 19 | transition: all .3 ease-in-out; 20 | 21 | &:active { 22 | position: relative; 23 | top: 1px; 24 | } 25 | 26 | &:hover { 27 | background: #cc9f52 !important; 28 | } 29 | } -------------------------------------------------------------------------------- /generators/app/templates/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as CommentItem } from './global/commentItem'; 2 | export { default as CommentList } from './global/commentList'; 3 | export { default as CustomNProgress } from './global/customNProgress'; 4 | export { default as Layout } from './global/layout'; 5 | export { default as PostItem } from './global/postItem'; 6 | export { default as PostList } from './global/postList'; 7 | export { default as CustomErrorBoundary } from './global/customErrorBoundary'; 8 | /* new-component-import-goes-here */ 9 | -------------------------------------------------------------------------------- /generators/app/templates/src/config/custom-environment-variables.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: 'NODE_ENV', 3 | }; 4 | -------------------------------------------------------------------------------- /generators/app/templates/src/config/default.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: 'development', 3 | }; 4 | -------------------------------------------------------------------------------- /generators/app/templates/src/config/development.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: 'development', 3 | }; 4 | -------------------------------------------------------------------------------- /generators/app/templates/src/config/production.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: 'production', 3 | }; 4 | -------------------------------------------------------------------------------- /generators/app/templates/src/config/testing.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: 'testing', 3 | }; 4 | -------------------------------------------------------------------------------- /generators/app/templates/src/contexts/index.ts: -------------------------------------------------------------------------------- 1 | /* new-context-import-goes-here */ 2 | -------------------------------------------------------------------------------- /generators/app/templates/src/enums/index.ts: -------------------------------------------------------------------------------- 1 | /* new-enum-import-goes-here */ 2 | -------------------------------------------------------------------------------- /generators/app/templates/src/hocs/index.ts: -------------------------------------------------------------------------------- 1 | /* new-hoc-import-goes-here */ -------------------------------------------------------------------------------- /generators/app/templates/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | /* new-hook-import-goes-here */ 2 | -------------------------------------------------------------------------------- /generators/app/templates/src/i18n.js: -------------------------------------------------------------------------------- 1 | const i18next = require('i18next'); 2 | const XHR = require('i18next-xhr-backend'); 3 | const LanguageDetector = require('i18next-browser-languagedetector'); 4 | 5 | const options = { 6 | fallbackLng: 'en', 7 | load: 'languageOnly', // we only provide en, de -> no region specific locals like en-US, de-DE 8 | 9 | // have a common namespace used around the full app 10 | ns: ['common'], 11 | defaultNS: 'common', 12 | 13 | debug: process.env.NODE_ENV !== 'production', 14 | saveMissing: true, 15 | 16 | interpolation: { 17 | escapeValue: false, // not needed for react!! 18 | formatSeparator: ',', 19 | format: (value, format, lng) => { 20 | if (format === 'uppercase') return value.toUpperCase(); 21 | return value; 22 | }, 23 | }, 24 | }; 25 | 26 | const i18nInstance = i18next; 27 | 28 | // for browser use xhr backend to load translations and browser lng detector 29 | if (process.browser) { 30 | i18nInstance 31 | .use(XHR) 32 | // .use(Cache) 33 | .use(LanguageDetector); 34 | } 35 | 36 | // initialize if not already initialized 37 | if (!i18nInstance.isInitialized) i18nInstance.init(options); 38 | 39 | // a simple helper to getInitialProps passed on loaded i18n data 40 | const getInitialProps = (req, namespaces) => { 41 | if (!namespaces) namespaces = i18nInstance.options.defaultNS; 42 | if (typeof namespaces === 'string') namespaces = [namespaces]; 43 | 44 | req.i18n.toJSON = () => null; // do not serialize i18next instance and send to client 45 | 46 | const initialI18nStore = {}; 47 | req.i18n.languages.forEach(l => { 48 | initialI18nStore[l] = {}; 49 | namespaces.forEach(ns => { 50 | initialI18nStore[l][ns] = 51 | (req.i18n.services.resourceStore.data[l] || {})[ns] || {}; 52 | }); 53 | }); 54 | 55 | return { 56 | i18n: req.i18n, // use the instance on req - fixed language on request (avoid issues in race conditions with lngs of different users) 57 | initialI18nStore, 58 | initialLanguage: req.i18n.language, 59 | }; 60 | }; 61 | 62 | module.exports = { 63 | getInitialProps, 64 | i18nInstance, 65 | I18n: i18next.default, 66 | }; 67 | -------------------------------------------------------------------------------- /generators/app/templates/src/lib/config.shim.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | import { get, has } from 'lodash'; 3 | 4 | export default { 5 | get: key => get(window.__CONFIG__, key), 6 | has: key => has(window.__CONFIG__, key), 7 | }; 8 | /* eslint-enable no-underscore-dangle */ 9 | -------------------------------------------------------------------------------- /generators/app/templates/src/lib/withI18next.js: -------------------------------------------------------------------------------- 1 | import { translate } from 'react-i18next'; 2 | import { getInitialProps, I18n } from '../i18n'; 3 | 4 | export const withI18next = (namespaces = ['common']) => ComposedComponent => { 5 | const Extended = translate(namespaces, { i18n: I18n, wait: process.browser })( 6 | ComposedComponent 7 | ); 8 | 9 | Extended.getInitialProps = async ctx => { 10 | const composedInitialProps = ComposedComponent.getInitialProps 11 | ? await ComposedComponent.getInitialProps(ctx) 12 | : {}; 13 | 14 | const i18nInitialProps = 15 | ctx.req && !process.browser ? getInitialProps(ctx.req, namespaces) : {}; 16 | 17 | return { 18 | ...composedInitialProps, 19 | ...i18nInitialProps, 20 | }; 21 | }; 22 | 23 | return Extended; 24 | }; 25 | -------------------------------------------------------------------------------- /generators/app/templates/src/models/comment.d.ts: -------------------------------------------------------------------------------- 1 | export interface IComment { 2 | readonly id: number; 3 | readonly postId: string; 4 | readonly name: string; 5 | readonly email: string; 6 | readonly body: string; 7 | } 8 | -------------------------------------------------------------------------------- /generators/app/templates/src/models/dispatchable.d.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch } from 'redux'; 2 | 3 | export interface IDispatchable { 4 | readonly dispatch: Dispatch | any; 5 | } 6 | -------------------------------------------------------------------------------- /generators/app/templates/src/models/index.d.ts: -------------------------------------------------------------------------------- 1 | export { IComment } from './comment'; 2 | export { IPost } from './post'; 3 | export { ILoadable } from './loadable'; 4 | export { IDispatchable } from './dispatchable'; 5 | /* new-interface-import-goes-here */ 6 | -------------------------------------------------------------------------------- /generators/app/templates/src/models/loadable.d.ts: -------------------------------------------------------------------------------- 1 | export interface ILoadable { 2 | readonly isLoading: boolean; 3 | } 4 | -------------------------------------------------------------------------------- /generators/app/templates/src/models/post.d.ts: -------------------------------------------------------------------------------- 1 | export interface IPost { 2 | readonly id: string; 3 | readonly title: string; 4 | readonly body: string; 5 | } 6 | -------------------------------------------------------------------------------- /generators/app/templates/src/next.config.js: -------------------------------------------------------------------------------- 1 | const withPlugins = require('next-compose-plugins'); 2 | const withTypescript = require('@zeit/next-typescript'); 3 | const withCSS = require('@zeit/next-css'); 4 | const withSass = require('@zeit/next-sass'); 5 | const withLess = require('@zeit/next-less'); 6 | const lessToJS = require('less-vars-to-js'); 7 | const withBundleAnalyzer = require('@zeit/next-bundle-analyzer'); 8 | const nextRuntimeDotenv = require('next-runtime-dotenv'); 9 | const fs = require('fs'); 10 | const path = require('path'); 11 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 12 | 13 | const withConfig = nextRuntimeDotenv({ 14 | public: ['API_URL'], 15 | }); 16 | 17 | // Where your antd-custom.less file lives 18 | const themeVariables = lessToJS(fs.readFileSync(path.resolve(__dirname, './styles/antd-custom.less'), 'utf8')); 19 | 20 | // fix: prevents error when .less files are required by node 21 | if (typeof require !== 'undefined') { 22 | // eslint-disable-next-line node/no-deprecated-api 23 | require.extensions['.less'] = file => {}; 24 | } 25 | 26 | module.exports = withPlugins( 27 | [ 28 | [withTypescript], 29 | [withCSS], 30 | [ 31 | withLess, 32 | { 33 | lessLoaderOptions: { 34 | javascriptEnabled: true, 35 | modifyVars: themeVariables, // make your antd custom effective 36 | }, 37 | }, 38 | ], 39 | [withSass], 40 | [withConfig], 41 | [withBundleAnalyzer], 42 | ], 43 | { 44 | analyzeServer: ['server', 'both'].includes(process.env.BUNDLE_ANALYZE), 45 | analyzeBrowser: ['browser', 'both'].includes(process.env.BUNDLE_ANALYZE), 46 | bundleAnalyzerConfig: { 47 | server: { 48 | analyzerMode: 'static', 49 | reportFilename: '../bundles/server.html', 50 | }, 51 | browser: { 52 | analyzerMode: 'static', 53 | reportFilename: '../bundles/client.html', 54 | }, 55 | }, 56 | webpack: config => { 57 | // Fixes npm packages that depend on `fs` module 58 | config.node = { 59 | fs: 'empty', 60 | }; 61 | // Added aliases 62 | config.resolve.alias = { 63 | ...config.resolve.alias, 64 | '@root': path.join(__dirname), 65 | config: path.resolve(__dirname, 'lib/config.shim'), 66 | }; 67 | if (config.mode === 'production') { 68 | if (Array.isArray(config.optimization.minimizer)) { 69 | config.optimization.minimizer.push(new OptimizeCSSAssetsPlugin({})); 70 | } 71 | } 72 | 73 | return config; 74 | }, 75 | } 76 | ); 77 | -------------------------------------------------------------------------------- /generators/app/templates/src/pages/_app.js: -------------------------------------------------------------------------------- 1 | // pages/_app.js 2 | import React from 'react'; 3 | import App, { Container } from 'next/app'; 4 | import { Provider } from 'react-redux'; 5 | import { PersistGate } from 'redux-persist/integration/react'; 6 | import { configureStore } from 'redux-store/createStore'; 7 | import { CustomErrorBoundary } from 'components'; 8 | 9 | const { store, persistor } = configureStore(); 10 | 11 | export default class MyApp extends App { 12 | static async getInitialProps({ Component, ctx }) { 13 | const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {}; 14 | return { pageProps }; 15 | } 16 | 17 | render() { 18 | const { Component, pageProps } = this.props; 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /generators/app/templates/src/pages/_document.js: -------------------------------------------------------------------------------- 1 | // ./pages/_document.js 2 | import React from 'react'; 3 | import Document, {Head, Main, NextScript} from 'next/document'; 4 | import htmlescape from 'htmlescape'; 5 | import config from 'config'; 6 | 7 | export default class MyDocument extends Document { 8 | render () { 9 | return ( 10 | 11 | 12 | 13 |
14 |