├── .eslintrc.js
├── .github
└── workflows
│ └── codeql-analysis.yml
├── .gitignore
├── .prettierrc.js
├── LICENSE
├── README.md
├── SECURITY.md
├── consoleaway.sh
├── next-env.d.ts
├── next.config.js
├── package.json
├── public
├── favicon.ico
└── vercel.svg
├── src
├── app
│ ├── next-env.d.ts
│ ├── redux
│ │ ├── counter
│ │ │ └── counter.slice.ts
│ │ ├── hooks.ts
│ │ ├── plant
│ │ │ └── plant.slice.ts
│ │ └── store.ts
│ ├── styles
│ │ ├── Home.module.css
│ │ └── globals.css
│ └── tsconfig.json
├── data
│ └── repositories
│ │ └── PlantRepositoryImpl.ts
├── domain
│ ├── entities
│ │ └── Plant.ts
│ ├── repositories
│ │ └── PlantRepository.ts
│ └── usecases
│ │ └── PlantService.ts
└── pages
│ ├── _app.tsx
│ ├── api
│ └── hello.ts
│ └── index.tsx
├── tsconfig.json
└── yarn.lock
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2021: true,
5 | },
6 | extends: [
7 | 'plugin:react/recommended',
8 | 'airbnb',
9 | 'prettier'
10 | ],
11 | parser: '@typescript-eslint/parser',
12 | parserOptions: {
13 | ecmaFeatures: {
14 | jsx: true,
15 | },
16 | ecmaVersion: 12,
17 | sourceType: 'module',
18 | },
19 | plugins: [
20 | 'react',
21 | '@typescript-eslint', 'simple-import-sort', "react-hooks"
22 | ],
23 | rules: {
24 | "simple-import-sort/imports": "error",
25 | "simple-import-sort/exports": "error",
26 | "import/first": "error",
27 | "import/newline-after-import": "error",
28 | "import/no-duplicates": "error",
29 | "react/react-in-jsx-scope": "off",
30 | "@typescript-eslint/explicit-function-return-type": "off",
31 | "@typescript-eslint/explicit-module-boundary-types": "off",
32 | "@typescript-eslint/no-explicit-any": "off",
33 | "camelcase": [2, {"properties": "always"}],
34 | "sort-imports": "off",
35 | "import/order": "off",
36 | "react/forbid-prop-types": "error",
37 | "react/forbid-foreign-prop-types": "error",
38 | "react/no-access-state-in-setstate": "error",
39 | "react/no-array-index-key": "error",
40 | "react/no-did-mount-set-state": "error",
41 | "react/no-did-update-set-state": "error",
42 | "react/no-direct-mutation-state": "error",
43 | "react/prop-types": "error",
44 | "react/jsx-no-target-blank": "error",
45 | "no-useless-computed-key": "error",
46 | "react-hooks/rules-of-hooks": "error",
47 | "react-hooks/exhaustive-deps": "warn",
48 | "import/extensions": "off",
49 | "react/jsx-props-no-spreading": "off",
50 | "react/require-default-props": "off",
51 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx", ".ts", ".tsx"] }],
52 | "no-unused-vars": "off",
53 | "@typescript-eslint/no-unused-vars": ["error"],
54 | "import/no-extraneous-dependencies": ["error", {"devDependencies": true}]
55 |
56 | },
57 | settings: {
58 | "import/resolver": {
59 | typescript: {}
60 | },
61 | },
62 | };
63 |
64 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ master ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ master ]
20 | schedule:
21 | - cron: '26 19 * * 2'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 |
28 | strategy:
29 | fail-fast: false
30 | matrix:
31 | language: [ 'javascript' ]
32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
33 | # Learn more:
34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
35 |
36 | steps:
37 | - name: Checkout repository
38 | uses: actions/checkout@v2
39 |
40 | # Initializes the CodeQL tools for scanning.
41 | - name: Initialize CodeQL
42 | uses: github/codeql-action/init@v1
43 | with:
44 | languages: ${{ matrix.language }}
45 | # If you wish to specify custom queries, you can do so here or in a config file.
46 | # By default, queries listed here will override any specified in a config file.
47 | # Prefix the list here with "+" to use these queries and those in the config file.
48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
49 |
50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
51 | # If this step fails, then you should remove it and run the build manually (see below)
52 | - name: Autobuild
53 | uses: github/codeql-action/autobuild@v1
54 |
55 | # ℹ️ Command-line programs to run using the OS shell.
56 | # 📚 https://git.io/JvXDl
57 |
58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
59 | # and modify them (or add more) to build your code if your project
60 | # uses a compiled language
61 |
62 | #- run: |
63 | # make bootstrap
64 | # make release
65 |
66 | - name: Perform CodeQL Analysis
67 | uses: github/codeql-action/analyze@v1
68 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # IDEs and editors
33 | .idea
34 | .project
35 | .classpath
36 | .c9/
37 | *.launch
38 | .settings/
39 | *.sublime-workspace
40 |
41 | # IDE - VSCode
42 | .vscode/*
43 | !.vscode/settings.json
44 | !.vscode/tasks.json
45 | !.vscode/launch.json
46 | !.vscode/extensions.json
47 |
48 | # misc
49 | .sass-cache
50 | connect.lock
51 | typings
52 |
53 | # Logs
54 | logs
55 | *.log
56 | npm-debug.log*
57 | yarn-debug.log*
58 | yarn-error.log*
59 |
60 | .history
61 | .idea
62 | .vscode
63 |
64 | # Dependency directories
65 |
66 | jspm_packages/
67 | node_modules/
68 |
69 | # Optional npm cache directory
70 | .npm
71 |
72 | # Optional eslint cache
73 | .eslintcache
74 |
75 | # Optional REPL history
76 | .node_repl_history
77 |
78 | # Output of 'npm pack'
79 | *.tgz
80 |
81 | # Yarn Integrity file
82 | .yarn-integrity
83 |
84 | # dotenv environment variables file
85 | .env
86 |
87 | # next.js build output
88 | .next
89 |
90 | # Lerna
91 | lerna-debug.log
92 |
93 | # System Files
94 | .DS_Store
95 | Thumbs.db
96 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: false,
3 | trailingComma: "all",
4 | singleQuote: false,
5 | printWidth: 120,
6 | tabWidth: 4
7 | };
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Janith Leanage
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react_js_clean_architecture
2 |
3 | ## Overview
4 | A React JS boilerplate that makes it easy and intuitive to implement [Uncle Bob's Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) in react js. This boilerplate provides basic structure that are designed according to the Clean Architecture.
5 |
6 | The concept is borrrowed to this wonderfull code.
7 |
8 | https://github.com/ShadyBoukhary/flutter_clean_architecture
9 |
10 | https://github.com/janithl/react-clean-arch
11 |
12 | Thanks to @ShadyBoukhary and @janithl
13 |
14 | ## Getting Started
15 |
16 | ```
17 | git clone https://github.com/bailabs/react_js_clean_architecture.git
18 | cd react_js_clean_architecture
19 | yarn install // or npm install
20 | yarn run dev // or npm yarn run dev
21 | ```
22 |
23 | ## Flutter Clean Architecture Primer
24 | ### Introduction
25 | It is architecture based on the book and blog by Uncle Bob. It is a combination of concepts taken from the Onion Architecture and other architectures. The main focus of the architecture is separation of concerns and scalability. It consists of four main modules: `App`, `Domain`, `Data`, and `Device`.
26 |
27 | ### The Dependency Rule
28 | **Source code dependencies only point inwards**. This means inward modules are neither aware of nor dependent on outer modules. However, outer modules are both aware of and dependent on inner modules. Outer modules represent the mechanisms by which the business rules and policies (inner modules) operate. The more you move inward, the more abstraction is present. The outer you move the more concrete implementations are present. Inner modules are not aware of any classes, functions, names, libraries, etc.. present in the outer modules. They simply represent **rules** and are completely independent from the implementations.
29 |
30 | ### Layers
31 |
32 | #### Domain
33 | The `Domain` module defines the business logic of the application. It is a module that is independent from the development platform i.e. it is written purely in the programming language and does not contain any elements from the platform. The reason for that is that `Domain` should only be concerned with the business logic of the application, not with the implementation details. This also allows for easy migration between platforms, should any issues arise.
34 |
35 | ##### Contents of Domain
36 | `Domain` is made up of several things.
37 | * **Entities**
38 | <<<<<<< HEAD
39 | * Enterprise-wide business rules
40 | * Made up of classes that can contain methods
41 | * Business objects of the application
42 | * Used application-wide
43 | * Least likely to change when something in the application changes
44 | * **Usecases**
45 | * Application-specific business rules
46 | * Encapsulate all the usecases of the application
47 | * Orchestrate the flow of data throughout the app
48 | * Should not be affected by any UI changes whatsoever
49 | * Might change if the functionality and flow of application change
50 | * **Repositories**
51 | * Abstract classes that define the expected functionality of outer layers
52 | * Are not aware of outer layers, simply define expected functionality
53 | * E.g. The `Login` usecase expects a `Repository` that has `login` functionality
54 | * Passed to `Usecases` from outer layers
55 | =======
56 | * Enterprise-wide business rules
57 | * Made up of classes that can contain methods
58 | * Business objects of the application
59 | * Used application-wide
60 | * Least likely to change when something in the application changes
61 | * **Usecases**
62 | * Application-specific business rules
63 | * Encapsulate all the usecases of the application
64 | * Orchestrate the flow of data throughout the app
65 | * Should not be affected by any UI changes whatsoever
66 | * Might change if the functionality and flow of application change
67 | * **Repositories**
68 | * Abstract classes that define the expected functionality of outer layers
69 | * Are not aware of outer layers, simply define expected functionality
70 | * E.g. The `Login` usecase expects a `Repository` that has `login` functionality
71 | * Passed to `Usecases` from outer layers
72 | >>>>>>> ea40ce18a7ffdb0eabd67b40cd7d0fa2db7b5a7e
73 |
74 | `Domain` represents the inner-most layer. Therefore, it the most abstract layer in the architecture.
75 |
76 | #### App
77 | `App` is the layer outside `Domain`. `App` crosses the boundaries of the layers to communicate with `Domain`. However, the **Dependency Rule** is never violated. Using `polymorphism`, `App` communicates with `Domain` using inherited class: classes that implement or extend the `Repositories` present in the `Domain` layer. Since `polymorphism` is used, the `Repositories` passed to `Domain` still adhere to the **Dependency Rule** since as far as `Domain` is concerned, they are abstract. The implementation is hidden behind the `polymorphism`.
78 |
79 | ##### Contents of App
80 | Since `App` is the presentation layer of the application, it is the most framework-dependent layer, as it contains the UI and the event handlers of the UI. For every page in the application, `App` defines at least 3 classes: a `Action`, a `Reducers`, and a `View`.
81 | * **ACTIONS**
82 | <<<<<<< HEAD
83 | * In a nutshell, actions are events. Actions send data from the application (user interactions, internal events such as API calls, and form submissions) to the store. The store gets information only from actions. Internal actions are simple JavaScript objects that have a type property (usually constant), describing the type of action and payload of information being sent to the store.
84 |
85 | ```
86 | {
87 | type: LOGIN_FORM_SUBMIT,
88 | payload: {username: ‘alex’, password: ‘123456’}
89 | }
90 | ```
91 | Actions are created with action creators. That sounds obvious, I know. They are just functions that return actions.
92 |
93 | ```
94 | function authUser(form) {
95 | return {
96 | type: LOGIN_FORM_SUBMIT,
97 | payload: form
98 | }
99 | }
100 | ```
101 | Calling actions anywhere in the app, then, is very easy. Use the dispatch method, like so:
102 |
103 | ```
104 | dispatch(authUser(form));
105 | ```
106 | * **REDUCERS**
107 |
108 | * We’ve already discussed what a reducer is in functional JavaScript. It’s based on the array reduce method, where it accepts a callback (reducer) and lets you get a single value out of multiple values, sums of integers, or an accumulation of streams of values. In Redux, reducers are functions (pure) that take the current state of the application and an action and then return a new state. Understanding how reducers work is important because they perform most of the work. Here is a very simple reducer that takes the current state and an action as arguments and then returns the next state:
109 |
110 | ```
111 | function handleAuth(state, action) {
112 | return _.assign({}, state, {
113 | auth: action.payload
114 | });
115 | }
116 | ```
117 | For more complex apps, using the combineReducers() utility provided by Redux is possible (indeed, recommended). It combines all of the reducers in the app into a single index reducer. Every reducer is responsible for its own part of the app’s state, and the state parameter is different for every reducer. The combineReducers() utility makes the file structure much easier to maintain.
118 |
119 | If an object (state) changes only some values, Redux creates a new object, the values that didn’t change will refer to the old object and only new values will be created. That’s great for performance. To make it even more efficient you can add Immutable.js.
120 |
121 | ```
122 | const rootReducer = combineReducers({
123 | handleAuth: handleAuth,
124 | editProfile: editProfile,
125 | changePassword: changePassword
126 | });
127 | ```
128 |
129 | * Extra
130 | * `Utility` classes (any commonly used functions like timestamp getters etc..)
131 | * `Constants` classes (`const` strings for convenience)
132 | * `Navigator` (if needed)
133 |
134 | #### Data
135 | Represents the data-layer of the application. The `Data` module, which is a part of the outermost layer, is responsible for data retrieval. This can be in the form of API calls to a server, a local database, or even both.
136 |
137 | ##### Contents of Data
138 | * **Repositories**
139 | * Every `Repository` **should** implement `Repository` from the **Domain** layer.
140 | * Using `polymorphism`, these repositories from the data layer can be passed across the boundaries of layers, starting from the `View` down to the `Usecases` through the `Controller` and `Presenter`.
141 | * Retrieve data from databases or other methods.
142 | * Responsible for any API calls and high-level data manipulation such as
143 | * Registering a user with a database
144 | * Uploading data
145 | * Downloading data
146 | * Handling local storage
147 | * Calling an API
148 | * **Models** (not a must depending on the application)
149 | * Extensions of `Entities` with the addition of extra members that might be platform-dependent. For example, in the case of local databases, this can be manifested as an `isDeleted` or an `isDirty` entry in the local database. Such entries cannot be present in the `Entities` as that would violate the **Dependency Rule** since **Domain** should not be aware of the implementation.
150 | * In the case of our application, models in the `Data` layer will not be necessary as we do not have a local database. Therefore, it is unlikely that we will need extra entries in the `Entities` that are platform-dependent.
151 | * **Mappers**
152 | * Map `Entity` objects to `Models` and vice-versa.
153 | * Static classes with static methods that receive either an `Entity` or a `Model` and return the other.
154 | * Only necessary in the presence of `Models`
155 | * Extra
156 | * `Utility` classes if needed
157 | * `Constants` classes if needed
158 |
159 | #### Device
160 | Part of the outermost layer, `Device` communicates directly with the platform i.e. Android and iOS. `Device` is responsible for Native functionality such as `GPS` and other functionality present within the platform itself like the filesystem. `Device` calls all Native APIs.
161 |
162 | ##### Contents of Data
163 | * **Devices**
164 | * Similar to `Repositories` in `Data`, `Devices` are classes that communicate with a specific functionality in the platform.
165 | * Passed through the layers the same way `Repositories` are pass across the boundaries of the layer: using polymorphism between the `App` and `Domain` layer. That means the `Controller` passes it to the `Presenter` then the `Presenter` passes it polymorphically to the `Usecase`, which receives it as an abstract class.
166 | * Extra
167 | * `Utility` classes if needed
168 | * `Constants` classes if needed
169 | =======
170 | * In a nutshell, actions are events. Actions send data from the application (user interactions, internal events such as API calls, and form submissions) to the store. The store gets information only from actions. Internal actions are simple JavaScript objects that have a type property (usually constant), describing the type of action and payload of information being sent to the store.
171 |
172 | ```
173 | {
174 | type: LOGIN_FORM_SUBMIT,
175 | payload: {username: ‘alex’, password: ‘123456’}
176 | }
177 | ```
178 | Actions are created with action creators. That sounds obvious, I know. They are just functions that return actions.
179 |
180 | ```
181 | function authUser(form) {
182 | return {
183 | type: LOGIN_FORM_SUBMIT,
184 | payload: form
185 | }
186 | }
187 | ```
188 | Calling actions anywhere in the app, then, is very easy. Use the dispatch method, like so:
189 |
190 | ```
191 | dispatch(authUser(form));
192 | ```
193 | * **REDUCERS**
194 |
195 | * We’ve already discussed what a reducer is in functional JavaScript. It’s based on the array reduce method, where it accepts a callback (reducer) and lets you get a single value out of multiple values, sums of integers, or an accumulation of streams of values. In Redux, reducers are functions (pure) that take the current state of the application and an action and then return a new state. Understanding how reducers work is important because they perform most of the work. Here is a very simple reducer that takes the current state and an action as arguments and then returns the next state:
196 |
197 | ```
198 | function handleAuth(state, action) {
199 | return _.assign({}, state, {
200 | auth: action.payload
201 | });
202 | }
203 | ```
204 | For more complex apps, using the combineReducers() utility provided by Redux is possible (indeed, recommended). It combines all of the reducers in the app into a single index reducer. Every reducer is responsible for its own part of the app’s state, and the state parameter is different for every reducer. The combineReducers() utility makes the file structure much easier to maintain.
205 |
206 | If an object (state) changes only some values, Redux creates a new object, the values that didn’t change will refer to the old object and only new values will be created. That’s great for performance. To make it even more efficient you can add Immutable.js.
207 |
208 | ```
209 | const rootReducer = combineReducers({
210 | handleAuth: handleAuth,
211 | editProfile: editProfile,
212 | changePassword: changePassword
213 | });
214 | ```
215 |
216 | * Extra
217 | * `Utility` classes (any commonly used functions like timestamp getters etc..)
218 | * `Constants` classes (`const` strings for convenience)
219 | * `Navigator` (if needed)
220 |
221 | #### Data
222 | Represents the data-layer of the application. The `Data` module, which is a part of the outermost layer, is responsible for data retrieval. This can be in the form of API calls to a server, a local database, or even both.
223 |
224 | ##### Contents of Data
225 | * **Repositories**
226 | * Every `Repository` **should** implement `Repository` from the **Domain** layer.
227 | * Using `polymorphism`, these repositories from the data layer can be passed across the boundaries of layers, starting from the `View` down to the `Usecases` through the `Controller` and `Presenter`.
228 | * Retrieve data from databases or other methods.
229 | * Responsible for any API calls and high-level data manipulation such as
230 | * Registering a user with a database
231 | * Uploading data
232 | * Downloading data
233 | * Handling local storage
234 | * Calling an API
235 | * **Models** (not a must depending on the application)
236 | * Extensions of `Entities` with the addition of extra members that might be platform-dependent. For example, in the case of local databases, this can be manifested as an `isDeleted` or an `isDirty` entry in the local database. Such entries cannot be present in the `Entities` as that would violate the **Dependency Rule** since **Domain** should not be aware of the implementation.
237 | * In the case of our application, models in the `Data` layer will not be necessary as we do not have a local database. Therefore, it is unlikely that we will need extra entries in the `Entities` that are platform-dependent.
238 | * **Mappers**
239 | * Map `Entity` objects to `Models` and vice-versa.
240 | * Static classes with static methods that receive either an `Entity` or a `Model` and return the other.
241 | * Only necessary in the presence of `Models`
242 | * Extra
243 | * `Utility` classes if needed
244 | * `Constants` classes if needed
245 |
246 | #### Device
247 | Part of the outermost layer, `Device` communicates directly with the platform i.e. Android and iOS. `Device` is responsible for Native functionality such as `GPS` and other functionality present within the platform itself like the filesystem. `Device` calls all Native APIs.
248 |
249 | ##### Contents of Data
250 | * **Devices**
251 | * Similar to `Repositories` in `Data`, `Devices` are classes that communicate with a specific functionality in the platform.
252 | * Passed through the layers the same way `Repositories` are pass across the boundaries of the layer: using polymorphism between the `App` and `Domain` layer. That means the `Controller` passes it to the `Presenter` then the `Presenter` passes it polymorphically to the `Usecase`, which receives it as an abstract class.
253 | * Extra
254 | * `Utility` classes if needed
255 | * `Constants` classes if needed
256 | >>>>>>> ea40ce18a7ffdb0eabd67b40cd7d0fa2db7b5a7e
257 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Use this section to tell people about which versions of your project are
6 | currently being supported with security updates.
7 |
8 | | Version | Supported |
9 | | ------- | ------------------ |
10 | | 5.1.x | :white_check_mark: |
11 | | 5.0.x | :x: |
12 | | 4.0.x | :white_check_mark: |
13 | | < 4.0 | :x: |
14 |
15 | ## Reporting a Vulnerability
16 |
17 | Use this section to tell people how to report a vulnerability.
18 |
19 | Tell them where to go, how often they can expect to get an update on a
20 | reported vulnerability, what to expect if the vulnerability is accepted or
21 | declined, etc.
22 |
--------------------------------------------------------------------------------
/consoleaway.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ######## ####### ######## ######## ######## ######### ######## #######
4 | # Title: ConsoleAway
5 | # Version: 0.9b
6 | # Author: |Ds|
7 | # URL: https://github.com/7Ds7/ConsoleAway/
8 | #
9 | # to comment out console.* from js and coffee files
10 | # do not expect it to work on minified files, use at your own risk
11 | #
12 | # ./consoleaway.sh -f [FILE]
13 | # To use on a single file
14 | #
15 | # ./ consoleaway.sh -r [FOLDER]
16 | # To recursively use on a folder
17 | #
18 | # WARNING: this will modify your files make sure you back them up
19 | ######## ####### ######## ######## ######## ######### ######## #######
20 |
21 | FILE_TMP=''
22 | PLATFORM=`uname`
23 |
24 | function ValidationsFile {
25 | if [[ -z "$1" ]]; then
26 | echo "-- Feed me a file --"
27 | exit
28 | else
29 | if [ ! -f $1 ]; then
30 | echo "-- File not found! --"
31 | exit
32 | fi
33 | echo ":: Ommm nomm nomm tasty file ::"
34 | fi
35 | }
36 |
37 | function ValidationsFolder {
38 | if [[ -z "$1" ]]; then
39 | echo "-- Feed me a folder --"
40 | exit
41 | else
42 | if [ ! -d $1 ]; then
43 | echo "-- Folder not found! --"
44 | exit
45 | fi
46 | echo ":: Ommm nomm nomm tasty folder ::"
47 | fi
48 | }
49 |
50 | function ProcessJs {
51 | # Different platform option to choose regular expressions as extended (modern) regular expressions
52 | case $PLATFORM in
53 | "Linux" ) FILE_TMP=`sed -r '/^[\s]|[\/]/! s/(console.(assert|clear|count|debug|dir|dirxml|error|group|groupCollapsed|groupEnd|info|log|profile|profileEnd|time|timeEnd|timeStamp|trace|warn)\((.*)\);?)/\/\/\1/g' $1`;;
54 | "Darwin" ) FILE_TMP=`sed -E '/^[\s]|[\/]/! s/(console.(assert|clear|count|debug|dir|dirxml|error|group|groupCollapsed|groupEnd|info|log|profile|profileEnd|time|timeEnd|timeStamp|trace|warn)\((.*)\);?)/\/\/\1/g' $1`;;
55 | * ) echo $PLARFORM " this OS is not predicted in the script" ;exit; # Default.
56 | esac
57 | cat /dev/null > $1
58 | echo "$FILE_TMP" > $1
59 | }
60 |
61 | function ProcessCoffee {
62 | # Different platform option to choose regular expressions as extended (modern) regular expressions
63 | case $PLATFORM in
64 | "Linux" ) FILE_TMP=`sed -r '/^[\s]|[#]/! s/(console.(assert|clear|count|debug|dir|dirxml|error|group|groupCollapsed|groupEnd|info|log|profile|profileEnd|time|timeEnd|timeStamp|trace|warn));?/\#\1/g' $1`;;
65 | "Darwin" ) FILE_TMP=`sed -E '/^[\s]|[#]/! s/(console.(assert|clear|count|debug|dir|dirxml|error|group|groupCollapsed|groupEnd|info|log|profile|profileEnd|time|timeEnd|timeStamp|trace|warn));?/\#\1/g' $1`;;
66 | * ) echo $PLARFORM " this OS is not predicted in the script" ;exit; # Default.
67 | esac
68 | cat /dev/null > $1
69 | echo "$FILE_TMP" > $1
70 | }
71 |
72 | function SingleFile {
73 | ValidationsFile $1
74 | FILE=$1
75 |
76 | if [[ ! $FILE == *.js && ! $FILE == *.coffee ]]; then
77 | echo '-- File is not a js or coffee file --'
78 | exit
79 | fi
80 |
81 | echo ':: Processing File -> '$1
82 | if [[ $FILE == *.js ]]; then
83 | ProcessJs $FILE
84 | else
85 | if [[ $FILE == *.coffee ]]; then
86 | ProcessCoffee $FILE
87 | fi
88 | fi
89 | echo ':: Done Consoles are gone ::'
90 | exit
91 | }
92 |
93 | function RecursiveFolder {
94 | ValidationsFolder $1
95 | for f in $(find $1 -name '*.ts' -or -name '*.jsx' -or -name '*.js' -or -name '*.coffee'); do
96 | echo 'Processing File -> '$f
97 | if [[ $f == *.js ]]; then
98 | ProcessJs $f
99 | elif [[ $f == *.coffee ]]; then
100 | ProcessCoffee $f
101 | elif [[ $f == *.ts ]]; then
102 | ProcessJs $f
103 | elif [[ $f == *.jsx ]]; then
104 | ProcessJs $f
105 | fi
106 | done
107 | echo ':: Done Consoles are gone ::'
108 | exit
109 | }
110 |
111 | function Usage {
112 | echo "Usage: `basename $0` options (-f -r) [FILE || FOLDER]"
113 | echo""
114 | echo "consoleaway -h"
115 | echo " Prints this message"
116 | echo ""
117 | echo "consoleaway -f [FILE.js || FILE.coffee]"
118 | echo " Process single file"
119 | echo ""
120 | echo "consoleaway -r [FOLDER]"
121 | echo " Process folder recursively"
122 | echo ""
123 | echo "WARNING: this will modify your files make sure you back them up"
124 | exit $E_OPTERROR # Exit and explain usage.
125 | # Usage: scriptname -options
126 | # Note: dash (-) necessary
127 | }
128 |
129 | # Here we observe how 'getopts' processes command-line arguments to script.
130 | # The arguments are parsed as "options" (flags) and associated arguments.
131 |
132 | NO_ARGS=0
133 | E_OPTERROR=85
134 |
135 | if [ $# -eq "$NO_ARGS" ] # Script invoked with no command-line args?
136 | then
137 | Usage
138 | fi
139 |
140 | while getopts ":frh" Option
141 | do
142 | case $Option in
143 | f ) SingleFile $2;;
144 | r ) RecursiveFolder $2;;
145 | h ) Usage;;
146 | * ) echo "Unimplemented option chosen.";exit; # Default.
147 | esac
148 | done
149 |
150 | shift $(($OPTIND - 1))
151 | # Decrements the argument pointer so it points to next argument.
152 | # $1 now references the first non-option item supplied on the command-line
153 | #+ if one exists.
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 |
5 | // NOTE: This file should not be edited
6 | // see https://nextjs.org/docs/basic-features/typescript for more information.
7 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | transpileModules: [
3 | "./src"
4 | ]
5 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react_next_js_clean_architecture",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "prettier --write src/. && eslint src --ext .tsx,.ts,.js,.jsx --fix && yarn run remove_console",
10 | "remove_console": ".\\consoleaway.sh -r src/",
11 | "deploy": "yarn run remove_console && yarn run lint && vercel deploy --prod"
12 | },
13 | "dependencies": {
14 | "@reduxjs/toolkit": "^1.5.1",
15 | "@types/react-redux": "^7.1.16",
16 | "next": "^11.1.3",
17 | "react": "17.0.2",
18 | "react-dom": "17.0.2",
19 | "react-redux": "^7.2.3"
20 | },
21 | "devDependencies": {
22 | "@types/node": "^15.3.0",
23 | "@types/prop-types": "^15.7.3",
24 | "@types/react": "^17.0.3",
25 | "@typescript-eslint/eslint-plugin": "^4.23.0",
26 | "@typescript-eslint/parser": "^4.23.0",
27 | "dotenv-webpack": "^7.0.2",
28 | "eslint": "^7.26.0",
29 | "eslint-config-airbnb": "^18.2.1",
30 | "eslint-config-airbnb-typescript": "^12.3.1",
31 | "eslint-config-prettier": "^8.3.0",
32 | "eslint-import-resolver-typescript": "^2.4.0",
33 | "eslint-plugin-import": "^2.22.1",
34 | "eslint-plugin-jsx-a11y": "^6.4.1",
35 | "eslint-plugin-prettier": "^3.4.0",
36 | "eslint-plugin-react": "^7.23.2",
37 | "eslint-plugin-react-hooks": "^4.2.0",
38 | "eslint-plugin-simple-import-sort": "^7.0.0",
39 | "prettier": "^2.3.0",
40 | "typescript": "^4.2.4"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bailabs/react_js_clean_architecture/052818ccbf653bbcf8cb4c6e09116aac1141d7cb/public/favicon.ico
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/src/app/redux/counter/counter.slice.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"
2 |
3 | interface CounterState {
4 | value: number
5 | }
6 |
7 | const initialState = { value: 0 } as CounterState
8 |
9 | const counterSlice = createSlice({
10 | name: "counter",
11 | initialState,
12 | reducers: {
13 | increment: (state) => ({
14 | ...state,
15 | value: state.value + 1,
16 | }),
17 | decrement: (state) => ({
18 | ...state,
19 | value: state.value === 0 ? 0 : state.value - 1,
20 | }),
21 | incrementByAmount: (state, action: PayloadAction) => ({
22 | ...state,
23 | value: state.value + action.payload,
24 | }),
25 | },
26 | })
27 |
28 | export const { increment, decrement, incrementByAmount } = counterSlice.actions
29 | export default counterSlice.reducer
30 |
--------------------------------------------------------------------------------
/src/app/redux/hooks.ts:
--------------------------------------------------------------------------------
1 | import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"
2 |
3 | import type { AppDispatch, RootState } from "./store"
4 |
5 | // Use throughout your app instead of plain `useDispatch` and `useSelector`
6 | export const useAppDispatch = () => useDispatch()
7 | export const useAppSelector: TypedUseSelectorHook = useSelector
8 |
--------------------------------------------------------------------------------
/src/app/redux/plant/plant.slice.ts:
--------------------------------------------------------------------------------
1 | import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"
2 |
3 | import PlantsRepositoryImpl from "../../../data/repositories/PlantRepositoryImpl"
4 | import Item from "../../../domain/entities/Plant"
5 | import PlantService from "../../../domain/usecases/PlantService"
6 | import type { RootState } from "../store"
7 | // Define a type for the slice state
8 | interface CounterState {
9 | loading: boolean
10 | plants: Array
11 | }
12 |
13 | // Define the initial state using that type
14 | const initialState: CounterState = {
15 | loading: false,
16 | plants: [],
17 | }
18 |
19 | export const fetchList = createAsyncThunk("plantSlice/fetchList", async () => {
20 | const plantRepo = new PlantsRepositoryImpl()
21 | const plantService = new PlantService(plantRepo)
22 | const plants = await plantService.GetPlants()
23 | return plants
24 | })
25 | export const plantSlice = createSlice({
26 | name: "plantSlice",
27 | // `createSlice` will infer the state type from the `initialState` argument
28 | initialState,
29 | reducers: {},
30 | extraReducers: (builder) => {
31 | builder.addCase(fetchList.fulfilled, (state, action) => ({
32 | ...state,
33 | plants: action.payload,
34 | loading: false,
35 | }))
36 | builder.addCase(fetchList.pending, (state) => ({
37 | ...state,
38 | loading: true,
39 | }))
40 | builder.addCase(fetchList.rejected, (state) => ({
41 | ...state,
42 | loading: false,
43 | }))
44 | },
45 | })
46 |
47 | // Other code such as selectors can use the imported `RootState` type
48 | export const plants = (state: RootState) => state.plantSlice.plants
49 |
50 | export default plantSlice.reducer
51 |
--------------------------------------------------------------------------------
/src/app/redux/store.ts:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit"
2 |
3 | import counterSlice from "./counter/counter.slice"
4 | import plantSlice from "./plant/plant.slice"
5 |
6 | export const store = configureStore({
7 | reducer: {
8 | plantSlice,
9 | counterSlice,
10 | },
11 | })
12 |
13 | // Infer the `RootState` and `AppDispatch` types from the store itself
14 | export type RootState = ReturnType
15 | // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
16 | export type AppDispatch = typeof store.dispatch
17 |
--------------------------------------------------------------------------------
/src/app/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | min-height: 100vh;
3 | padding: 0 0.5rem;
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | align-items: center;
8 | }
9 |
10 | .main {
11 | padding: 5rem 0;
12 | flex: 1;
13 | display: flex;
14 | flex-direction: column;
15 | justify-content: center;
16 | align-items: center;
17 | }
18 |
19 | .footer {
20 | width: 100%;
21 | height: 100px;
22 | border-top: 1px solid #eaeaea;
23 | display: flex;
24 | justify-content: center;
25 | align-items: center;
26 | }
27 |
28 | .footer img {
29 | margin-left: 0.5rem;
30 | }
31 |
32 | .footer a {
33 | display: flex;
34 | justify-content: center;
35 | align-items: center;
36 | }
37 |
38 | .title a {
39 | color: #0070f3;
40 | text-decoration: none;
41 | }
42 |
43 | .title a:hover,
44 | .title a:focus,
45 | .title a:active {
46 | text-decoration: underline;
47 | }
48 |
49 | .title {
50 | margin: 0;
51 | line-height: 1.15;
52 | font-size: 4rem;
53 | }
54 |
55 | .title,
56 | .description {
57 | text-align: center;
58 | }
59 |
60 | .description {
61 | line-height: 1.5;
62 | font-size: 1.5rem;
63 | }
64 |
65 | .code {
66 | background: #fafafa;
67 | border-radius: 5px;
68 | padding: 0.75rem;
69 | font-size: 1.1rem;
70 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New,
71 | monospace;
72 | }
73 |
74 | .grid {
75 | display: flex;
76 | align-items: center;
77 | justify-content: center;
78 | flex-wrap: wrap;
79 | max-width: 800px;
80 | margin-top: 3rem;
81 | }
82 |
83 | .card {
84 | margin: 1rem;
85 | flex-basis: 45%;
86 | padding: 1.5rem;
87 | text-align: left;
88 | color: inherit;
89 | text-decoration: none;
90 | border: 1px solid #eaeaea;
91 | border-radius: 10px;
92 | transition: color 0.15s ease, border-color 0.15s ease;
93 | }
94 |
95 | .card:hover,
96 | .card:focus,
97 | .card:active {
98 | color: #0070f3;
99 | border-color: #0070f3;
100 | }
101 |
102 | .card h3 {
103 | margin: 0 0 1rem 0;
104 | font-size: 1.5rem;
105 | }
106 |
107 | .card p {
108 | margin: 0;
109 | font-size: 1.25rem;
110 | line-height: 1.5;
111 | }
112 |
113 | .logo {
114 | height: 1em;
115 | }
116 |
117 | @media (max-width: 600px) {
118 | .grid {
119 | width: 100%;
120 | flex-direction: column;
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/app/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans,
6 | Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": false,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve"
16 | },
17 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
18 | "exclude": ["node_modules"]
19 | }
20 |
--------------------------------------------------------------------------------
/src/data/repositories/PlantRepositoryImpl.ts:
--------------------------------------------------------------------------------
1 | import Plant from "../../domain/entities/Plant"
2 | import PlantRepository from "../../domain/repositories/PlantRepository"
3 |
4 | export default class PlantRepositoryImpl implements PlantRepository {
5 | jsonUrl =
6 | "https://gist.githubusercontent.com/janithl/6bfbd787a0361c170ac760e8fb5ba0fd/raw/a0ffacb7c0fc21a0266371f632cf4107f80362f4/itemlist.json"
7 |
8 | async GetPlants(): Promise {
9 | const res = await fetch(this.jsonUrl)
10 | const jsonData = await res.json()
11 | return jsonData.map((item: Plant) => ({ id: item.id, name: item.name }))
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/domain/entities/Plant.ts:
--------------------------------------------------------------------------------
1 | export default class Plant {
2 | id: number
3 |
4 | name: string
5 |
6 | constructor(id: number, name: string) {
7 | this.id = id
8 | this.name = name
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/domain/repositories/PlantRepository.ts:
--------------------------------------------------------------------------------
1 | import Plant from "../entities/Plant"
2 |
3 | export default interface PlantRepository {
4 | GetPlants(): Promise
5 | }
6 |
--------------------------------------------------------------------------------
/src/domain/usecases/PlantService.ts:
--------------------------------------------------------------------------------
1 | import Plant from "../entities/Plant"
2 | import PlantRepository from "../repositories/PlantRepository"
3 |
4 | export default class PlantServiceImpl {
5 | plantRepo: PlantRepository
6 |
7 | constructor(ir: PlantRepository) {
8 | this.plantRepo = ir
9 | }
10 |
11 | async GetPlants(): Promise {
12 | return this.plantRepo.GetPlants()
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import "../app/styles/globals.css"
2 |
3 | import PropTypes from "prop-types"
4 | import { Provider } from "react-redux"
5 |
6 | import { store } from "../app/redux/store"
7 |
8 | function MyApp({ Component, pageProps }) {
9 | return (
10 |
11 |
12 |
13 | )
14 | }
15 |
16 | MyApp.propTypes = {
17 | Component: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).isRequired,
18 | pageProps: PropTypes.shape({}),
19 | }
20 |
21 | export default MyApp
22 |
--------------------------------------------------------------------------------
/src/pages/api/hello.ts:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default (req, res) => {
4 | res.status(200).json({ name: "John Doe" })
5 | }
6 |
--------------------------------------------------------------------------------
/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { decrement, increment, incrementByAmount } from "app/redux/counter/counter.slice"
2 | import Plant from "domain/entities/Plant"
3 | import { useState } from "react"
4 |
5 | import { useAppDispatch, useAppSelector } from "../app/redux/hooks"
6 | import { fetchList } from "../app/redux/plant/plant.slice"
7 |
8 | export default function Home() {
9 | const [amount, setAmount] = useState(0)
10 |
11 | const { plants, loading, value } = useAppSelector((state) => ({
12 | plants: state.plantSlice.plants,
13 | loading: state.plantSlice.loading,
14 | value: state.counterSlice.value,
15 | }))
16 |
17 | const dispatch = useAppDispatch()
18 |
19 | const handleAmountChange = (e: any) => {
20 | setAmount(parseFloat(e.target.value))
21 | }
22 |
23 | const handleClick = () => {
24 | dispatch(fetchList())
25 | }
26 |
27 | const handleSubmitAmount = () => {
28 | dispatch(incrementByAmount(amount))
29 | }
30 |
31 | return (
32 |