stadtenergie v1.14.4
StadtEnergie
description
Requirements
- Node.js
^8.15.1
- npm
^5.0.0
Install
First, clone the project:
$ git clone --depth=1 git@github.com:mobilabsolutions/stadtenergie.git <THE_NAME>
$ cd THE_NAME
Then install dependencies and check to see if it works.
$ npm install # Install project dependencies
$ npm run dev # Compile and launch
npm run ... | Description |
---|---|
dev | Runs next which starts Next.js in development mode. |
build | Runs next build which builds the application for production usage. |
start | Runs next start which starts a Next.js production server. |
update-dependencies | Update the dependencies of the project. |
storybook | Run storybook for the ui component. |
storybook:build | Build storybook for the ui component. |
storybook:deploy | deploy ui components. |
Stack
- react
- nextjs
- redux
- typescript
- styled components
- redux-sagas
Concepts
Components
Components are the smallest building blocks of the application. The components, modules and the pages could render this components. All the third party components are wrapped in our components folder, so if we want to customize or even change is much more easy.
Services
Mimic's the services provided by the BE.
The rule it's to split by pathname. So for e.g if the BE has an ${apiConfig.ENDPOINT}/my-service
, a myService.ts
should be create on the services folder, with all the calls needed for the project.
The calls should be stateless functions that return a promise
(args) => Promise<Result|Error>
Pages
A page is a React Component exported file in the pages directory. Each page is associated with a route based on its file name. So if we have a pages/homepage
we can access this route on the browser .../homepage
.
Modules
A module represents related code of a specific concept. You should aim to reduce the coupling of related Modules by making the dependency between two Modules unidirectional. Every module has an index.ts file which serves as the entry point for other modules, this way, a module can choose which parts it exposes in its public API(components, actions, types and constants).
A module structure
├── ModuleA
│ ├── index.ts # Entry point of the module (container)
│ ├── components # Modules that exclusevely used on the module
│ ├────── ModuleA.tsx # Component for the ModuleA
│ ├── store # Module store
│ │ ├── index.ts # Entry point for the store
│ │ ├── actions.ts # Module action
│ │ ├── reducer.ts # Module reducer
│ │ ├── contants.ts # Module actions
│ │ ├── selectors.ts # Module selectors
│ │ ├── sagas # Module sagas
A module could be global(the app needs it to work properly, such as User, App, etc) or a feature one.
The global module register the reducers and sagas on the root store like usual.
The feature module could contain it's own store
and it's injected to the root store only when needed. The store should be used only inside the respective module.
Because we use a strategy of lazy registering reducer/saga we need to include useInjectReducer
, useInjectSaga
if needed. This way the first load of the app will be much more faster because don't need to initialize unnecessary reducers or sagas.
The useInjectSaga
should be used for side effects on the module, communication with global store (like user, app, or ui(modal, etc)).
The communication between 2 global modules should be done on the root sagas store/sagas
const MODULE_KEY = 'moduleA';
const ContainerInfo = () => {
//inject reducer dynamically
useInjectReducer({ key: MODULE_KEY, reducer });
//inject saga dynamically
useInjectSaga({ key: MODULE_KEY, saga });
const moduleAData = useSelector(({ moduleA }) => moduleA);
...
};
Communication between modules
To communicate between 2 modules should be made by the sagas(aggregations).
Folder Structure
.
├── config # Project and build configurations
│ ├── webpack # The webpack configuration for each env
│ ├── testing # Testing configuration
├── src # Application source code
│ ├── index.ts # Application bootstrap and rendering
│ ├── components # Global Reusable Stateless components
│ ├── modules # Global Reusable modules
│ │ ├── ModuleA # ModuleA feature
│ │ ├──── store # Include (action.ts, reducer.ts, saga.ts)
│ │ ├──── components # Components that are used on the module
│ │ └──── index.ts # The entry point of the module
│ ├── configs # Global configuration peer environment
│ ├── core # Core
│ ├── layouts # Components that dictate major page structure
│ │ └── Main # Main layout
│ │ └── Authenticated # Authenticated layout
│ │ └── index.ts # Main file for layout
│ ├── pages # Main route definitions and async split points
│ │ ├── index.ts # Bootstrap main application routes with store
│ │ ├── route1 # Route
│ │ │ └── index.ts # The route component
│ │ ├── route2 # Route
│ │ │ └── index.ts # The route component
│ ├── store # Redux-specific pieces
│ │ ├── configureStore.ts # Create and instrument redux store
│ │ └── reducers.ts # Reducer registry
│ │ └── sagas.ts # Sagas registry
│ └── assets # Assets folder
│ │ ├── styles # Styles folder
│ │ │ ├── style.css # Style entry point
│ │ │ ├── global.css # Global styles
│ │ ├── fonts # Fonts folder
│ │ ├── images # Images folder
└── tests # Unit tests
Styles
...
Styleguide
Run npm run styleguide
to start style a guide dev server.
Run npm run styleguide:build
to build a static version.
Every component should have a README.md in which the component should be described by a few simple examples and a short description.
For a more detailed explaination you can check the styleguidist docs over here
i18n
The app is prepared to work with i18n, to do we just need to create a file on public/static/locales
where the name of the file will be the namespace. For example: pages.landing.json
the namespace will be pages.homes
try always use the the folder structure for the translations file. pages.AAA
, components.BBB
, modules.CCC
.
The translation files are json.
pages.home.json
{
"h1": "A simple example"
}
to use on the code we just need to:
...
import { useTranslation, includeDefaultNamespaces } from 'utils/i18n';
const Page: NextComponentType = () => {
const { t: translation, i18n } = useTranslation();
return (
<React.Fragment>
{translation('pages.home:title')}
<button
onClick={() =>
i18n.changeLanguage(i18n.language === 'en' ? 'de' : 'en')
}
>
{translation('change-locale')}
</button>
{i18n.language}
</React.Fragment>
);
};
Page.getInitialProps = async () => ({
namespacesRequired: includeDefaultNamespaces(['pages.home'])
});
export default Page;
To change the language we use i18n.changeLanguage('en')
To get the current language we use i18n.language
Content Management
The app content is controlled by a non-technical content manager through Phrase.com In-Context Editor. Therefore it is necessary to keep the content in sync with Phrase.com.
First setup phrase cli using:
npm run phrase:setup
Then every once in a while, when you have added new keys, run:
npm run phrase:sync
NOTE:
To make sure the content manager's data doesn't get lost in any way, the data in phrase.com has precedence to the local data, in case of conflicts. This has some drawbacks when a key is already published to phrase.com:
- If you locally edit the value of such a key, it will get overwritten by the value of the key in phrase.com, after running the command.
- If you locally delete such a key, it will get re-added after running the command.
- If you locally rename such a key, the new name and the old name will both exist in phrase.com as two keys with the same value, and both will be added back locally after running the command.
Therefore, try to stick with additions only, and for editions or deletions, ask Fardin to fix things in phrase.com. Don't edit or delete locally.
Globals
These are global variables available to you anywhere in your source code.
Variable | Description |
---|---|
process.env.NODE_ENV | the active NODE_ENV when the build started |
__DEV__ | True when process.env.NODE_ENV is development |
__PROD__ | True when process.env.NODE_ENV is production |
__TEST__ | True when process.env.NODE_ENV is test |
__LOCAL__ | True when process.env.NODE_ENV is local |
__COVERAGE__ | True when process.env.NODE_ENV is test and uses the watch flag |
Release
To make a new release of the project just run npm run release
on master branch and after that git push --follow-tags origin master
.
The commits should follow the conventional commits
standard so it can bump the versions and generate correctly the changelog.
To learn more about conventional commits visit the website
Commit message format
semantic-release uses the commit messages to determine the type of changes in the codebase. Following formalized conventions for commit messages, semantic-release automatically determines the next semantic version number, generates a changelog and publishes the release.
By default semantic-release uses Angular Commit Message Conventions. The commit message format can be changed with the preset
or config
options of the @semantic-release/commit-analyzer and @semantic-release/release-notes-generator plugins.
Here is an example of the release type that will be done based on a commit messages:
Commit message | Release type |
---|---|
fix(pencil): stop graphite breaking when too much pressure applied | Patch Release |
feat(pencil): add 'graphiteWidth' option | Minor Release |
perf(pencil): remove graphiteWidth option BREAKING CHANGE: The graphiteWidth option has been removed. The default graphite width of 10mm is always used for performance reasons. | Major Release |
We use commitlint
to force commit messages.
commitlint
checks if your commit messages meet the conventional commit format.
In general the pattern mostly looks like this:
type(scope?): subject #scope is optional
Real world examples can look like this:
chore: run tests on travis ci
fix(server): send cors headers
feat(blog): add comment section
Common types according to commitlint-config-conventional (based on the the Angular convention) can be:
- build
- ci
- chore
- docs
- feat
- fix
- perf
- refactor
- revert
- style
- test
Routes
Why use Routing when using Next?
The problem when using Next, blocks us from using translations in the routes.
How to use it?
Instead of using Router.push() we use a method in the file /src/utils/routes.ts
, push
Push
const push = (route: any, options?: any) => {
Router.push(route, DE_NextConfigRoutes[route.pathname || route], options);
};
What to do
To be able to use route translations, what you need to do is:
Add a constant to the
routes.ts
in englishExport the just added english constant
Add a constant to the
routes.ts
in german withDE_
in the back of the nameAdd both constants as a
[KEY] => VALUE
to the DE_NextConfigRoutes making the
KEY english
and VALUE german
Go to where you want to push the route
Import the method and the english route
Use as below:
...
/**
* ORDER_PROCESS_ROUTE is the english constant
* Use only the route name
*/
push(ORDER_PROCESS_ROUTE)
//OR
/**
* Use with URL options
*/
push({
pathname: ORDER_PROCESS_ROUTE,
query: { step: `${stepIndex}` }
});
//OR
/**
* Use with URL options and other options
*/
push({
pathname: ORDER_PROCESS_ROUTE,
query: { step: `${stepIndex}` }
}, { shallow: true });
...
4 years ago