Repository: Armour/express-webpack-react-redux-typescript-boilerplate
Branch: master
Commit: 9f9435307de4
Files: 174
Total size: 146.8 KB
Directory structure:
gitextract_w64eli_i/
├── .appveyor.yml
├── .babelrc
├── .circleci/
│ └── config.yml
├── .commitlintrc.yml
├── .dockerignore
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .github/
│ ├── CODE_OF_CONDUCT.md
│ ├── COMMIT_CONVENTION.md
│ ├── CONTRIBUTING.md
│ ├── ISSUE_TEMPLATE.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── main.workflow
├── .gitignore
├── .stylelintignore
├── .stylelintrc
├── Dockerfile
├── LICENSE
├── README.md
├── __mocks__/
│ ├── fileMock.tsx
│ └── react-i18next.tsx
├── backend/
│ ├── config.json
│ ├── controllers/
│ │ └── notes.js
│ ├── db/
│ │ └── index.js
│ ├── jsconfig.json
│ ├── redis-data/
│ │ └── .gitkeep
│ ├── routes/
│ │ ├── index.js
│ │ └── notes.js
│ ├── server.js
│ └── sql/
│ ├── data.sql
│ └── schema.sql
├── docker-compose.yml
├── frontend/
│ ├── public/
│ │ ├── browserconfig.xml
│ │ ├── index.ejs
│ │ ├── locales/
│ │ │ ├── en/
│ │ │ │ ├── common.json
│ │ │ │ ├── homePage.json
│ │ │ │ ├── notFoundPage.json
│ │ │ │ ├── parallaxPage.json
│ │ │ │ └── reactPage.json
│ │ │ ├── jp/
│ │ │ │ ├── common.json
│ │ │ │ ├── homePage.json
│ │ │ │ ├── notFoundPage.json
│ │ │ │ ├── parallaxPage.json
│ │ │ │ └── reactPage.json
│ │ │ └── zh/
│ │ │ ├── common.json
│ │ │ ├── homePage.json
│ │ │ ├── notFoundPage.json
│ │ │ ├── parallaxPage.json
│ │ │ └── reactPage.json
│ │ ├── manifest.webmanifest
│ │ └── robots.txt
│ └── src/
│ ├── App.tsx
│ ├── components/
│ │ ├── ContentLoader/
│ │ │ ├── __tests__/
│ │ │ │ ├── __snapshots__/
│ │ │ │ │ └── contentLoader.spec.tsx.snap
│ │ │ │ └── contentLoader.spec.tsx
│ │ │ ├── contentLoader.scss
│ │ │ ├── contentLoader.tsx
│ │ │ └── index.tsx
│ │ ├── Dropdown/
│ │ │ ├── __tests__/
│ │ │ │ ├── __snapshots__/
│ │ │ │ │ └── dropdown.spec.tsx.snap
│ │ │ │ └── dropdown.spec.tsx
│ │ │ ├── dropdown.tsx
│ │ │ └── index.tsx
│ │ ├── Footer/
│ │ │ ├── __tests__/
│ │ │ │ ├── __snapshots__/
│ │ │ │ │ └── footer.spec.tsx.snap
│ │ │ │ └── footer.spec.tsx
│ │ │ ├── footer.tsx
│ │ │ └── index.tsx
│ │ └── Header/
│ │ ├── __tests__/
│ │ │ ├── __snapshots__/
│ │ │ │ └── header.spec.tsx.snap
│ │ │ └── header.spec.tsx
│ │ ├── header.scss
│ │ ├── header.tsx
│ │ └── index.tsx
│ ├── i18n/
│ │ └── index.tsx
│ ├── index.tsx
│ ├── pages/
│ │ ├── HomePage/
│ │ │ ├── __tests__/
│ │ │ │ ├── __snapshots__/
│ │ │ │ │ └── homePage.spec.tsx.snap
│ │ │ │ └── homePage.spec.tsx
│ │ │ ├── components/
│ │ │ │ ├── Carousel/
│ │ │ │ │ ├── __tests__/
│ │ │ │ │ │ ├── __snapshots__/
│ │ │ │ │ │ │ └── carousel.spec.tsx.snap
│ │ │ │ │ │ └── carousel.spec.tsx
│ │ │ │ │ ├── carousel.tsx
│ │ │ │ │ ├── constants/
│ │ │ │ │ │ └── carousel.tsx
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── Pushpin/
│ │ │ │ │ ├── __tests__/
│ │ │ │ │ │ ├── __snapshots__/
│ │ │ │ │ │ │ └── pushpin.spec.tsx.snap
│ │ │ │ │ │ └── pushpin.spec.tsx
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ ├── pushpin.scss
│ │ │ │ │ └── pushpin.tsx
│ │ │ │ └── TranslationButton/
│ │ │ │ ├── __tests__/
│ │ │ │ │ ├── __snapshots__/
│ │ │ │ │ │ └── translationButton.spec.tsx.snap
│ │ │ │ │ └── translationButton.spec.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── translationButton.tsx
│ │ │ ├── homePage.scss
│ │ │ ├── homePage.tsx
│ │ │ └── index.tsx
│ │ ├── NotFoundPage/
│ │ │ ├── __tests__/
│ │ │ │ ├── __snapshots__/
│ │ │ │ │ └── notFoundPage.spec.tsx.snap
│ │ │ │ └── notFoundPage.spec.tsx
│ │ │ ├── index.tsx
│ │ │ ├── notFoundPage.scss
│ │ │ └── notFoundPage.tsx
│ │ ├── ParallaxPage/
│ │ │ ├── __tests__/
│ │ │ │ ├── __snapshots__/
│ │ │ │ │ └── parallaxPage.spec.tsx.snap
│ │ │ │ └── parallaxPage.spec.tsx
│ │ │ ├── components/
│ │ │ │ └── PrismCodes/
│ │ │ │ ├── __tests__/
│ │ │ │ │ ├── __snapshots__/
│ │ │ │ │ │ └── prismCodes.spec.tsx.snap
│ │ │ │ │ └── prismCodes.spec.tsx
│ │ │ │ ├── constants/
│ │ │ │ │ └── prismCodes.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ ├── prismCodes.scss
│ │ │ │ └── prismCodes.tsx
│ │ │ ├── index.tsx
│ │ │ ├── parallaxPage.scss
│ │ │ └── parallaxPage.tsx
│ │ └── ReactPage/
│ │ ├── __tests__/
│ │ │ ├── __snapshots__/
│ │ │ │ └── reactPage.spec.tsx.snap
│ │ │ └── reactPage.spec.tsx
│ │ ├── components/
│ │ │ ├── FetchNote/
│ │ │ │ ├── __tests__/
│ │ │ │ │ ├── __snapshots__/
│ │ │ │ │ │ └── fetchNote.spec.tsx.snap
│ │ │ │ │ └── fetchNote.spec.tsx
│ │ │ │ ├── fetchNote.scss
│ │ │ │ ├── fetchNote.tsx
│ │ │ │ └── index.tsx
│ │ │ └── TodoLayout/
│ │ │ ├── __tests__/
│ │ │ │ ├── __snapshots__/
│ │ │ │ │ └── todoLayout.spec.tsx.snap
│ │ │ │ └── todoLayout.spec.tsx
│ │ │ ├── components/
│ │ │ │ ├── TodoFooter/
│ │ │ │ │ ├── __tests__/
│ │ │ │ │ │ ├── __snapshots__/
│ │ │ │ │ │ │ └── todoFooter.spec.tsx.snap
│ │ │ │ │ │ └── todoFooter.spec.tsx
│ │ │ │ │ ├── components/
│ │ │ │ │ │ └── TodoFilter/
│ │ │ │ │ │ ├── __tests__/
│ │ │ │ │ │ │ ├── __snapshots__/
│ │ │ │ │ │ │ │ └── todoFilter.spec.tsx.snap
│ │ │ │ │ │ │ └── todoFilter.spec.tsx
│ │ │ │ │ │ ├── index.tsx
│ │ │ │ │ │ ├── todoFilter.scss
│ │ │ │ │ │ └── todoFilter.tsx
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── todoFooter.tsx
│ │ │ │ ├── TodoInput/
│ │ │ │ │ ├── __tests__/
│ │ │ │ │ │ ├── __snapshots__/
│ │ │ │ │ │ │ └── todoInput.spec.tsx.snap
│ │ │ │ │ │ └── todoInput.spec.tsx
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── todoInput.tsx
│ │ │ │ └── TodoList/
│ │ │ │ ├── __tests__/
│ │ │ │ │ ├── __snapshots__/
│ │ │ │ │ │ └── todoList.spec.tsx.snap
│ │ │ │ │ └── todoList.spec.tsx
│ │ │ │ ├── components/
│ │ │ │ │ └── Todo/
│ │ │ │ │ ├── __tests__/
│ │ │ │ │ │ ├── __snapshots__/
│ │ │ │ │ │ │ └── todo.spec.tsx.snap
│ │ │ │ │ │ └── todo.spec.tsx
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ ├── todo.scss
│ │ │ │ │ └── todo.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── todoList.tsx
│ │ │ ├── index.tsx
│ │ │ ├── todoLayout.scss
│ │ │ └── todoLayout.tsx
│ │ ├── index.tsx
│ │ ├── reactPage.scss
│ │ └── reactPage.tsx
│ ├── reducers/
│ │ └── index.tsx
│ ├── router.tsx
│ ├── sagas/
│ │ └── index.tsx
│ ├── sass/
│ │ ├── global.scss
│ │ └── variables.scss
│ ├── services/
│ │ ├── notes/
│ │ │ ├── actions.tsx
│ │ │ ├── apis.tsx
│ │ │ ├── constants.tsx
│ │ │ ├── reducer.tsx
│ │ │ ├── sagas.tsx
│ │ │ └── types.d.ts
│ │ └── todos/
│ │ ├── __test__/
│ │ │ ├── actions.spec.tsx
│ │ │ └── reducer.spec.tsx
│ │ ├── actions.tsx
│ │ ├── constants.tsx
│ │ ├── reducer.tsx
│ │ └── types.d.ts
│ ├── store/
│ │ └── index.tsx
│ ├── types/
│ │ └── global.d.ts
│ └── utils/
│ └── index.tsx
├── package.json
├── tsconfig.json
├── tslint.json
├── webpack.config.base.babel.js
├── webpack.config.dev.babel.js
├── webpack.config.dll.babel.js
├── webpack.config.prod.babel.js
└── webpack.config.profile.babel.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .appveyor.yml
================================================
environment:
nodejs_version: "11"
install:
- ps: Install-Product node $env:nodejs_version
- yarn install
build_script:
- yarn build
test_script:
- yarn test
cache:
- "%LOCALAPPDATA%\\Yarn"
================================================
FILE: .babelrc
================================================
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": [
[
"@babel/plugin-proposal-class-properties",
{
"loose": true,
}
],
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-transform-runtime",
"react-hot-loader/babel",
[
"prismjs",
{
"languages": [
"javascript",
"css",
"markup",
"cpp",
"bash",
"docker",
"go",
"json",
"markdown",
"python",
"jsx",
"tsx",
"scss",
"typescript"
],
"plugins": [
"autolinker",
"command-line"
],
"theme": "default",
"css": true
}
]
]
}
================================================
FILE: .circleci/config.yml
================================================
# Javascript Node CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
#
version: 2
jobs:
build:
working_directory: ~
docker:
- image: circleci/node:11
steps:
- checkout
- run:
name: Install Docker client
command: |
set -x
VER="18.09.1"
curl -L -o /tmp/docker.tgz https://download.docker.com/linux/static/stable/x86_64/docker-$VER.tgz
tar -xz -C /tmp -f /tmp/docker.tgz
sudo cp -r /tmp/docker/* /usr/bin
- run:
name: Install Docker Compose
command: |
set -x
VER="1.23.2"
curl -L https://github.com/docker/compose/releases/download/$VER/docker-compose-`uname -s`-`uname -m` > /tmp/docker-compose
sudo cp /tmp/docker-compose /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
- setup_remote_docker
- run:
name: Run Docker Compose
command: docker-compose up --build -d
- run:
name: Install dependencies
command: yarn install
- run:
name: Run test
command: yarn test
================================================
FILE: .commitlintrc.yml
================================================
extends: ['armour']
================================================
FILE: .dockerignore
================================================
# General
.DS_Store
*~
*.swp
*.log
# Thumbnails
._*
Thumbs.db
# Trash folder or files
.Trashes
.Trash-*
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Editor config folder
.idea/
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# Code coverage
coverage/
.coveralls.yml
# Misc
__mocks__/
.circleci/
.github/
node_modules/
dist/
.appveyor.yml
.*lintrc*
.*lintignore*
yarn.lock
================================================
FILE: .editorconfig
================================================
# http://editorconfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# Trailing space override for markdown file
[*.md]
trim_trailing_whitespace = false
# Indentation override for js(x), ts(x) files
[*.{js,jsx,ts,tsx}]
indent_size = 2
indent_style = space
# Indentation override for css files
[*.{css,styl,scss,less,sass}]
indent_size = 2
indent_style = space
# Indentation override for html files
[*.html]
indent_size = 2
indent_style = space
# Indentation override for config files
[*.{json,yml}]
indent_size = 2
indent_style = space
# Minified JavaScript files shouldn't be changed
[**.min.js]
indent_style = ignore
insert_final_newline = ignore
================================================
FILE: .eslintignore
================================================
node_modules/
dist/
coverage/
================================================
FILE: .eslintrc
================================================
{
"env": {
"browser": true,
"node": true,
"es6": true
},
"extends": [
"airbnb"
],
"rules": {
"import/export": "error",
"import/first": "error",
"import/no-commonjs": "error",
"import/no-deprecated": "error",
"import/no-duplicates": "error",
"import/no-unresolved": "error",
"import/no-webpack-loader-syntax": "error",
"linebreak-style": "off",
"indent": [
"error",
2
],
"no-console": [
"error",
{
"allow": [
"warn",
"error",
"info"
]
}
],
"quotes": [
"error",
"single",
{
"allowTemplateLiterals": true
}
]
}
}
================================================
FILE: .gitattributes
================================================
# Details per file setting:
# text These files should be normalized (i.e. convert CRLF to LF).
# binary These files are binary and should be left untouched.
# Auto detect text files and perform LF normalization
* text=auto
# The above will handle all files NOT found below
# SOURCE CODE
*.bat text eol=crlf
*.css text
*.js text
*.jsx text
*.ejs text
*.html text
*.less text
*.sass text
*.scss text
*.sh text eol=lf
*.sql text
*.ts text
*.tsx text
# DOCKER
*.dockerignore text
Dockerfile text
# COMPILED FILES
*.dll binary
*.dylib binary
*.exe binary
*.so binary
# DOCUMENTATION
*.md text
*.txt text
LICENSE text
# CONFIGS
.editorconfig text
.gitattributes text
.gitignore text
*.conf text
*.config text
*.json text
*.lock text
*.toml text
*.yaml text
*.yml text
# GRAPHICS
*.ai binary
*.bmp binary
*.eps binary
*.gif binary
*.heic binary
*.ico binary
*.jpeg binary
*.jpg binary
*.pdf binary
*.png binary
*.psb binary
*.psd binary
*.svg text
*.tif binary
*.tiff binary
*.wbmp binary
*.webp binary
# AUDIO
*.aac binary
*.flac binary
*.m4a binary
*.mid binary
*.midi binary
*.mp3 binary
*.mp4a binary
*.mpga binary
*.oga binary
*.ogg binary
*.wav binary
*.weba binary
# VIDEO
*.3g2 binary
*.3gp binary
*.asf binary
*.avi binary
*.f4v binary
*.fla binary
*.flv binary
*.m4v binary
*.mov binary
*.mp4 binary
*.mpeg binary
*.mpg binary
*.ogv binary
*.qt binary
*.swf binary
*.webm binary
# ARCHIVES
*.7z binary
*.gz binary
*.jar binary
*.rar binary
*.tar binary
*.zip binary
# FONTS
*.eot binary
*.otf binary
*.ttf binary
*.woff binary
*.woff2 binary
================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
education, socio-economic status, nationality, personal appearance, race,
religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html)
[homepage]: https://www.contributor-covenant.org
================================================
FILE: .github/COMMIT_CONVENTION.md
================================================
# Git Commit Message Convention
> This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular).
## TL;DR
Messages must be matched by the following regex:
``` js
/^(Revert: )?(Feature|Fix|Docs|Improve|Config|Example|Refactor|Style|Test|Build|CI)(\(.+\))?: .{1,80}/
```
## Commit Message Format
Each commit message consists of a **header**, a **body** and a **footer**. The header has a special format that includes a **type**, a **scope** and a **subject**:
```text
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```
The **header** is mandatory and the **scope** of the header is optional.
Any line of the commit message cannot be longer 100 characters! This allows the message to be easier
to read on GitHub as well as in various git tools.
The footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.
Samples: (even more [samples](https://github.com/Armour/express-webpack-react-redux-typescript-boilerplate/commits/master))
```text
Docs(changelog): update changelog to beta.5
```
```text
Feature($browser): onUrlChange event (popstate/hashchange/polling)
Added new event to $browser:
- forward popstate event if available
- forward hashchange event if popstate not available
- do polling when neither popstate nor hashchange available
Breaks $browser.onHashChange, which was removed (use onUrlChange instead)
```
```text
Fix(release): need to depend on latest rxjs and zone.js
The version in our package.json gets copied to the one we publish, and users need the latest of these.
Closes #123, #245, #992
BREAKING CHANGE: isolate scope bindings definition has changed and
the inject option for the directive controller injection was removed.
To migrate the code follow the example below:
Before:
scope: {
myAttr: 'attribute',
myBind: 'bind',
myExpression: 'expression',
myEval: 'evaluate',
myAccessor: 'accessor'
}
After:
scope: {
myAttr: '@',
myBind: '@',
myExpression: '&',
// myEval - usually not useful, but in cases where the expression is assignable, you can use '='
myAccessor: '=' // in directive's template change myAccessor() to myAccessor
}
The removed `inject` wasn't generaly useful for directives so there should be no code using it.
```
### Revert
If the commit reverts a previous commit, it should begin with `Revert: `, followed by the header of the reverted commit. In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
### Type
Must be one of the following:
* **Build**: Changes that affect the build system or external dependencies (example scopes: gulp, npm, yarn)
* **CI**: Changes to CI related configuration files and scripts (example scopes: travis, circle, browserstack)
* **Config**: Changes to other configuration files (example scopes: webpack, babel, docker)
* **Docs**: Documentation only changes (example scopes: readme, changelog)
* **Example**: Changes for example code
* **Feature**: A new feature
* **Fix**: A bug fix
* **Improve**: Backwards-compatible enhancement changes
* **Refactor**: A code change that neither fixes a bug nor adds a feature
* **Style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
* **Test**: Changes for testing code
If the prefix is `Feature` or `Fix`, it will appear in the changelog. However if there is any [BREAKING CHANGE](#footer), the commit will always appear in the changelog.
### Scope
The scope could be anything specifying place of the commit change. For example `core`, `compiler`, `ssr`, `v-model`, `transition` etc...
### Subject
The subject contains succinct description of the change:
* use the imperative, present tense: "change" not "changed" nor "changes"
* don't capitalize the first letter
* no dot (.) at the end
### Body
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
The body should include the motivation for the change and contrast this with previous behavior.
### Footer
The footer should contain any information about **Breaking Changes** and is also the place to
reference GitHub issues that this commit **Closes**.
**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.
A detailed explanation can be found in this [document](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit)
================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing
## Code of Conduct
Help us keep express-webpack-react-redux-typescript-boilerplate open and inclusive. Please read and follow the [Code of Conduct](https://github.com/Armour/express-webpack-react-redux-typescript-boilerplate/blob/master/.github/CODE_OF_CONDUCT.md).
## Found a Bug
If you find a bug in the source code, you can help us by [submitting an issue](#submitting-an-issue) to our [GitHub Repository](https://github.com/Armour/express-webpack-react-redux-typescript-boilerplate). Even better, you can [submit a Pull Request](#submitting-a-pull-request) with a fix.
## Missing a Feature
You can *request* a new feature by [submitting an issue](#submitting-an-issue) to our GitHub Repository. If you would like to *implement* a new feature, please submit an issue with a proposal for your work first, to be sure that we can use it. Please consider what kind of change it is:
* For a **Major Feature**, first open an issue and outline your proposal so that it can be discussed. This will also allow us to better coordinate our efforts, prevent duplication of work, and help you to craft the change so that it is successfully accepted into the project.
* **Small Features** can be crafted and directly [submitted as a Pull Request](#submitting-a-pull-request).
## Submission Guidelines
### Submitting an Issue
Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available.
We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs, we will systematically ask you to provide a minimal reproduction scenario. Having a live, reproducible scenario gives us a wealth of important information without going back & forth to you with additional questions.
We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal plunk. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it.
Unfortunately, we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that doesn't have enough info to be reproduced.
You can file new issues by filling out the [new issue form](https://github.com/Armour/express-webpack-react-redux-typescript-boilerplate/issues/new).
### Submitting a Pull Request
Before you submit your Pull Request (PR) consider the following guidelines:
1. Search [GitHub](https://github.com/Armour/express-webpack-react-redux-typescript-boilerplate/pulls) for an open or closed PR that relates to your submission. You don't want to duplicate effort.
1. Fork this repo.
1. Make your changes in a new git branch.
```shell
git checkout -b my-new-feature master
```
1. Commit your changes using a descriptive commit message that follows our [commit message convention](#commit-message-convention). Adherence to these conventions is necessary because release notes are automatically generated from these messages.
```shell
git commit -am 'Add some feature'
```
1. Push your branch.
```shell
git push origin my-new-feature
```
1. Send a pull request :D
That's it! Thank you for your contribution!
#### After your pull request is merged
After your pull request is merged, you can safely delete your branch and pull the changes
from the main (upstream) repository:
* Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows:
```shell
git push origin --delete my-new-feature
```
* Check out the master branch:
```shell
git checkout master -f
```
* Delete the local branch:
```shell
git branch -D my-new-feature
```
* Update your master with the latest upstream version:
```shell
git pull
```
## Commit Message Convention
We have very precise rules over how our git commit messages can be formatted. This leads to **more readable messages** that are easy to follow when looking through the **project history**. But also, we use the git commit messages to **generate the change log**.
Please read and follow the [Commit Message Format](https://github.com/Armour/express-webpack-react-redux-typescript-boilerplate/blob/master/.github/COMMIT_CONVENTION.md).
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
<!--
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION.
-->
<!-- Please search GitHub for a similar issue or PR before submitting -->
# I'm submitting a
<!--
E.g.
bug report,
feature request,
performance issue,
regression (a behavior that used to work and stopped working in a new release),
documentation issue or request,
or others... (please describe)
-->
## Current behavior
<!-- Describe how the issue manifests. -->
## Expected behavior
<!-- Describe what the desired behavior would be. -->
## Minimal reproduction of the problem with instructions
<!--
For bug reports please provide the *STEPS TO REPRODUCE* and if possible a *MINIMAL DEMO* of the problem via github repo or similar tools.
-->
## What is the motivation / use case for changing the behavior
<!-- Describe the motivation or the concrete use case. -->
## Environment
<!-- Anything may be useful? Platform, Operating system version, IDE, package manager, HTTP server, ... -->
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--
Please make sure to read the Pull Request Guidelines:
https://github.com/Armour/express-webpack-react-redux-typescript-boilerplate/blob/master/.github/CONTRIBUTING.md#submitting-a-pull-request
-->
<!-- PULL REQUEST TEMPLATE -->
**Make sure the PR fulfills these requirements:**
- When resolving a specific issue, make sure it's referenced in the PR's title (e.g. `Closes #xxx[,#xxx]`, where "xxx" is the issue number)
- If adding a **new feature**, the PR's description includes: A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it)
- If this PR introduce a **breaking change**, please describe the impact and migration path for existing applications
**What kind of change does this PR introduce?**
<!--
E.g.
bugfix,
feature,
code style update,
refactor,
build-related changes,
or others... (please describe)
-->
**More information:**
================================================
FILE: .github/main.workflow
================================================
workflow "Deploy on Heroku" {
on = "push"
resolves = [
"verify",
]
}
# Login
action "login" {
uses = "actions/heroku@master"
args = "container:login"
secrets = ["HEROKU_API_KEY"]
}
# Push
action "push" {
needs = ["login"]
uses = "actions/heroku@master"
args = ["container:push", "--app", "$HEROKU_APP", "web"]
secrets = ["HEROKU_API_KEY"]
env = {
HEROKU_APP = "express-react-typescript"
}
}
# Release
action "deploy" {
needs = ["push"]
uses = "actions/heroku@master"
args = ["container:release", "--app", "$HEROKU_APP", "web"]
secrets = ["HEROKU_API_KEY"]
env = {
HEROKU_APP = "express-react-typescript"
}
}
# Verify
action "verify" {
needs = ["deploy"]
uses = "actions/heroku@master"
args = ["apps:info", "$HEROKU_APP"]
secrets = ["HEROKU_API_KEY"]
env = {
HEROKU_APP = "express-react-typescript"
}
}
================================================
FILE: .gitignore
================================================
# General
.DS_Store
*~
*.swp
*.log
# Thumbnails
._*
Thumbs.db
# Trash folder or files
.Trashes
.Trash-*
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Editor config folder
.idea/
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# Code coverage
coverage/
.coveralls.yml
# Misc
node_modules/
dist/
# Redis data
backend/redis-data/*
!backend/redis-data/.gitkeep
================================================
FILE: .stylelintignore
================================================
node_modules/
dist/
coverage/
================================================
FILE: .stylelintrc
================================================
{
"extends": "stylelint-config-standard"
}
================================================
FILE: Dockerfile
================================================
FROM node:alpine
ARG PORT=${PORT}
WORKDIR /usr/src/app
COPY . /usr/src/app/
RUN apk add --no-cache --update make gcc libc-dev libpng-dev automake autoconf libtool nasm
RUN yarn install
RUN yarn build
EXPOSE ${PORT}
CMD ["yarn", "serve"]
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017 Chong Guo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# express-webpack-react-redux-typescript-boilerplate
[](https://david-dm.org/Armour/express-webpack-react-redux-typescript-boilerplate)
[](https://circleci.com/gh/Armour/express-webpack-react-redux-typescript-boilerplate/tree/master)
[](https://ci.appveyor.com/api/projects/status/github/Armour/express-webpack-react-redux-typescript-boilerplate?svg=true&branch=master)
[](https://coveralls.io/github/Armour/express-webpack-react-redux-typescript-boilerplate?branch=master)
[](http://makeapullrequest.com)
[](https://github.com/facebook/jest)
[](https://opensource.org/licenses/MIT)
[](https://github.com/Armour/Jarvis)
## Example
* [Demo Page](https://express-react-typescript.herokuapp.com/) - contains classic todo list, async server call, and 404 page with random [moe images](https://github.com/Armour/express-webpack-react-redux-typescript-boilerplate/tree/master/frontend/src/pages/NotFoundPage/assets/images). (Support multi-language, currently English, Chinese, and Japanese)



## Stack
* [x] [yarn](https://github.com/yarnpkg/yarn) - dependency manager
* [x] [express](http://expressjs.com/) - node.js web framework for backend
* [x] [postgresql](https://www.postgresql.org/) - advanced open source database
* [x] [materialize](http://materializecss.com/) - a modern responsive front-end framework based on Material Design
* [x] [sass](https://github.com/sass/sass) - CSS pre-processors
* [x] [postcss](https://github.com/postcss/postcss) - CSS post-processor
* [x] [css-modules](https://github.com/css-modules/css-modules) - for default scoped/local css
* [x] [typescript](https://github.com/Microsoft/TypeScript) - a typed superset of javascript that scales
* [x] [webpack 4](https://github.com/webpack/webpack) - module bundler
* [x] [webpack-dev-server](https://github.com/webpack/webpack-dev-server) - serves a webpack app in development mode with hot reload
* [x] followed [ES6 standard](https://github.com/lukehoban/es6features)
* [x] [babel](https://babeljs.io/) - a JavaScript compiler that compile ES6 to ES5
* [x] [react](https://facebook.github.io/react/) - a JavaScript library for building user interfaces
* [x] [react-hot-loader 4](https://github.com/gaearon/react-hot-loader) - hot module reload!
* [x] [react-router 4](https://github.com/ReactTraining/react-router) - declarative routing for React
* [x] [react-redux 6](https://github.com/reactjs/react-redux) - the official react bindings for [redux 4](https://github.com/reactjs/redux) (a predictable state container for js apps)
* [x] [react-saga](https://github.com/redux-saga/redux-saga/) - make redux asynchronous flows easy to read, write and test, the replacement for [redux-thunk](https://github.com/reduxjs/redux-thunk)
* [x] [connected-react-router 6](https://github.com/supasate/connected-react-router) - a redux binding for react-router 4, the replacement for [react-router-redux v5](https://github.com/ReactTraining/react-router/tree/master/packages/react-router-redux)
* [x] [react-i18next](https://github.com/i18next/react-i18next) - internationalization for react done right
* [x] [immutable.js](https://github.com/facebook/immutable-js/) - persistent Immutable data structures for react redux state management
* [x] [editorconfig](http://editorconfig.org/) - maintain consistent coding styles between different editors and IDEs
* [x] [eslint](http://eslint.org/) - lint javascript files (.js, .jsx)
* [x] [tslint](https://palantir.github.io/tslint/) - lint typescript files (.ts, .tsx)
* [x] [stylelint](https://stylelint.io/) - lint style files (.css, .scss)
* [x] [commitlint](https://github.com/marionebl/commitlint) - lint git commit messages
* [x] [jest](https://facebook.github.io/jest/) - painless javascript testing
* [x] [coveralls](https://coveralls.io/) - test coverage
* [x] [husky](https://github.com/typicode/husky) - git hooks
* [x] [circle-ci 2](https://circleci.com/) - continuous integration tool for testing and deployment
* [x] [heroku](https://www.heroku.com/) - a platform as a service (PaaS) that enables developers to build, run, and operate applications entirely in the cloud.
* [x] [docker](https://github.com/docker/docker) - the open-source application container engine
* [x] [RESTful API design](https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design) - follow RESTful api design best practice
## How to run the sample code
### Prerequisite
* `yarn` or `npm`
* (optional) `docker` with `docker-compose`
### Quickest way
The easiest way to run the example project is to use `docker-compose`:
```bash
docker-compose up --build
```
that's it :)
You can also follow instructions below if you want to customize it.
### Install project dependencies
Go to project root directory:
```bash
yarn install
```
If you find permission problem when trying to install yarn globally, check [this](https://github.com/yarnpkg/yarn/issues/1060#issuecomment-268160528) out.
### Setup database and session store
You can either
* setup `postgresql` and `redis` using docker images:
```bash
docker-compose up -d postgres redis
```
or
* maintain it by yourself, if so, make sure you set the right config in `backend/config.json`.
### Build & Run
On development (with hot reload):
```bash
yarn dev
```
On production (with [terser](https://github.com/terser-js/terser) and other optimazitions):
```bash
yarn prod
```
## Profile assets bundle
Thanks to [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer), assets bundle can be analyzed and optimized through [DLL Plugin](https://webpack.js.org/plugins/dll-plugin/).
```bash
yarn profile
```
## Run test
```bash
yarn test
```
## Code coverage
```bash
yarn coverage
```
## Deployment
Every push on master branch will trigger [Github Actions](.github/main.workflow) for heroku deployment.
## Contributing
See [CONTRIBUTING.md](https://github.com/Armour/express-webpack-react-redux-typescript-boilerplate/blob/master/.github/CONTRIBUTING.md)
## License
[MIT License](https://github.com/Armour/express-webpack-react-redux-typescript-boilerplate/blob/master/LICENSE)
================================================
FILE: __mocks__/fileMock.tsx
================================================
export default 'test-file-stub';
================================================
FILE: __mocks__/react-i18next.tsx
================================================
export const Translation = ({ children }: any) => children((k: string) => k, { i18n: {} });
================================================
FILE: backend/config.json
================================================
{
"pgsql_hostname_dev": "localhost",
"pgsql_hostname_prod": "postgres",
"pgsql_username": "docker",
"pgsql_password": "docker",
"pgsql_database": "docker",
"redis_hostname_dev": "localhost",
"redis_hostname_prod": "redis",
"redis_port": "6379",
"http_port": "3003"
}
================================================
FILE: backend/controllers/notes.js
================================================
import status from 'http-status';
import db from '../db';
export const getAllNotes = async (req, res) => {
try {
const { rows: notes } = await db.query('SELECT * FROM notes ORDER BY id', []);
return res.status(status.OK).json({
data: {
notes,
},
error: null,
});
} catch (e) {
return res.status(status.INTERNAL_SERVER_ERROR).json({
data: null,
error: {
message: e.message,
// You can also add below fields for better error displaying
// code - error code for your project
// developerMessage - debug message for developers
},
});
}
};
export const getNote = async (req, res) => {
try {
const { id } = req.params;
const { rows: notes } = await db.query('SELECT * FROM notes WHERE id = $1', [id]);
if (notes === undefined || notes.length === 0) {
return res.status(status.NOT_FOUND).json({
data: null,
error: {
message: `Note with id ${id} not found.`,
},
});
}
return res.status(status.OK).json({
data: {
note: notes[0],
},
error: null,
});
} catch (e) {
return res.status(status.INTERNAL_SERVER_ERROR).json({
data: null,
error: {
message: e.message,
},
});
}
};
export const addNote = async (req, res) => {
try {
const { content } = req.body;
const { rows: notes } = await db.query('INSERT INTO notes(content) VALUES($1) RETURNING *', [content]);
if (notes === undefined || notes.length === 0) {
return res.status(status.NOT_FOUND).json({
data: null,
error: {
message: 'Insert note failed.',
},
});
}
return res.status(status.OK).json({
data: {
note: notes[0],
},
error: null,
});
} catch (e) {
return res.status(status.INTERNAL_SERVER_ERROR).json({
data: null,
error: {
message: e.message,
},
});
}
};
export const editNote = async (req, res) => {
try {
const { id } = req.params;
const { content } = req.body;
const { rows: notes } = await db.query('UPDATE notes SET content = $1 WHERE id = $2 RETURNING *', [content, id]);
if (notes === undefined || notes.length === 0) {
return res.status(status.NOT_FOUND).json({
data: null,
error: {
message: `Update note with id ${id} failed.`,
},
});
}
return res.status(status.OK).json({
data: {
note: notes[0],
},
error: null,
});
} catch (e) {
return res.status(status.INTERNAL_SERVER_ERROR).json({
data: null,
error: {
message: e.message,
},
});
}
};
export const removeNote = async (req, res) => {
try {
const { id } = req.params;
const { rows: notes } = await db.query('DELETE FROM notes WHERE id = $1 RETURNING *', [id]);
if (notes === undefined || notes.length === 0) {
return res.status(status.NOT_FOUND).json({
data: null,
error: {
message: `Delete note with id ${id} failed.`,
},
});
}
return res.status(status.OK).json({
data: {
note: notes[0],
},
error: null,
});
} catch (e) {
return res.status(status.INTERNAL_SERVER_ERROR).json({
data: null,
error: {
message: e.message,
},
});
}
};
================================================
FILE: backend/db/index.js
================================================
import { Pool } from 'pg';
import config from '../config.json';
const isProduction = process.env.NODE_ENV === 'production';
let pool;
// Create a postgresql connection pool
if (process.env.DATABASE_URL !== undefined) {
// For Heroku Postgres deployment
// See https://devcenter.heroku.com/articles/heroku-postgresql#connecting-in-node-js
pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: true,
});
} else {
// All config is optional, the environment variables will be used if the config is not present
pool = new Pool({
host: isProduction
? config.pgsql_hostname_prod
: config.pgsql_hostname_dev, // Default: process.env.PGHOST
database: config.pgsql_database, // Default: process.env.PGDATABASE
user: config.pgsql_username, // Default: process.env.PGUSER
password: config.pgsql_password, // Default: process.env.PGPASSWORD
port: config.pgsql_port, // Default: process.env.PGPORT
});
}
// Initializes a connection pool
export default {
query: (text, params) => pool.query(text, params),
pool,
};
================================================
FILE: backend/jsconfig.json
================================================
{
"compilerOptions": {
"baseUrl": "."
}
}
================================================
FILE: backend/redis-data/.gitkeep
================================================
================================================
FILE: backend/routes/index.js
================================================
import express from 'express';
import notesRtr from './notes';
const router = express.Router();
router.use('/notes', notesRtr);
export default router;
================================================
FILE: backend/routes/notes.js
================================================
import express from 'express';
import {
getAllNotes, getNote, addNote, editNote, removeNote,
} from '../controllers/notes';
const router = express.Router();
router.get('/', getAllNotes);
router.get('/:id', getNote);
router.post('/', addNote);
router.put('/:id', editNote);
router.delete('/:id', removeNote);
export default router;
================================================
FILE: backend/server.js
================================================
import express from 'express';
import path from 'path';
import logger from 'morgan';
import cookieParser from 'cookie-parser';
import bodyParser from 'body-parser';
import compression from 'compression';
import helmet from 'helmet';
import session from 'express-session';
import connectRedis from 'connect-redis';
import 'colors';
import config from './config.json';
import router from './routes/index';
const app = express();
const isProduction = process.env.NODE_ENV === 'production';
const port = process.env.PORT || config.http_port;
const RedisStore = connectRedis(session);
const server = require('http').createServer(app);
if (isProduction) {
app.use(helmet());
app.disable('x-powered-by');
app.use(logger('combined'));
app.set('trust proxy', 1);
} else {
app.use(logger('dev'));
}
app.use(compression());
app.use(bodyParser.json({
limit: '20mb',
}));
app.use(bodyParser.urlencoded({
limit: '20mb',
extended: true,
}));
app.use(cookieParser());
app.use(session({
store: new RedisStore({
host: isProduction ? config.redis_hostname_prod : config.redis_hostname_dev,
port: config.redis_port,
}),
secret: 'keyboard cat',
saveUninitialized: false,
resave: false,
cookie: {
httpOnly: !isProduction,
secure: isProduction,
},
}));
// api router
app.use('/api/v1', router);
if (isProduction) {
// Serve dist files if is production mode
const distPath = path.resolve(__dirname, '../frontend/dist/prod');
app.use(express.static(distPath));
app.get('*', (_, res) => {
res.sendFile(`${distPath}/index.html`);
});
} else {
// Return 404 for non-exist api
app.get('*', (req, res) => {
res.status(404).send(`Api not exist for ${req.url}`);
});
}
// Start server listen on specific port
server.listen(port, (error) => {
if (error) {
console.error(`\n${error}`);
server.close();
process.exit(1);
}
if (isProduction) {
console.info(`\nExpress: Listening on port ${port}, open up http://localhost:${port}/ in your broswer!\n`.green);
} else {
console.info(`\nExpress: Serve api on http://localhost:${port}/ \n`);
}
});
const stopHandler = (signal) => {
console.error(`\nExit process in responding to %s`.red, signal);
server.close();
process.exit(1);
};
process.on('SIGTERM', stopHandler, 'SIGTERM');
process.on('SIGINT', stopHandler, 'SIGINT');
process.on('SIGHUP', stopHandler, 'SIGINT');
================================================
FILE: backend/sql/data.sql
================================================
INSERT INTO notes (content) VALUES ('note data 1');
INSERT INTO notes (content) VALUES ('note data 2');
================================================
FILE: backend/sql/schema.sql
================================================
Create Table notes (
id serial primary key,
content text not null,
created timestamptz default now()
);
================================================
FILE: docker-compose.yml
================================================
version: '3.7'
services:
web:
image: cguo/express-webpack-react-redux-typescript-boilerplate
build: # ignored when deploying a stack in swarm mode or kubernetes
context: .
args:
- PORT=${PORT}
ports:
- "${PORT}:${PORT}"
depends_on: # ignored when deploying a stack in swarm mode or kubernetes
- postgres
- redis
restart: always # ignored when deploying a stack in swarm mode or kubernetes
deploy: # ignored by docker-compose
replicas: 3
restart_policy:
condition: on-failure
postgres:
image: postgres:alpine
volumes:
- ./backend/sql/schema.sql:/docker-entrypoint-initdb.d/1-schema.sql
- ./backend/sql/data.sql:/docker-entrypoint-initdb.d/2-data.sql
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
ports:
- "5432:5432"
restart: always # ignored when deploying a stack in swarm mode or kubernetes
deploy: # ignored by docker-compose
restart_policy:
condition: on-failure
redis:
image: redis:alpine
ports:
- "6379:6379"
volumes:
- ./backend/redis-data:/data
restart: always # ignored when deploying a stack in swarm mode or kubernetes
deploy: # ignored by docker-compose
restart_policy:
condition: on-failure
command: redis-server --appendonly yes
================================================
FILE: frontend/public/browserconfig.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#000000</TileColor>
</tile>
</msapplication>
</browserconfig>
================================================
FILE: frontend/public/index.ejs
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="Full-stack boilerplate that using express with webpack, react and typescript!">
<meta name="theme-color" content="#2196f3">
<title><%= htmlWebpackPlugin.options.title %></title>
<meta name="apple-mobile-web-app-title" content="Boilerplate">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="167x167" href="/apple-touch-icon-167x167.png">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png">
<link rel="apple-touch-startup-image" href="/apple-touch-startup-icon-1024x1024.png">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#2196f3">
<link rel="manifest" href="/manifest.webmanifest">
<link rel="shortcut icon" href="/favicon.ico">
</head>
<body>
<noscript>
For full functionality of this site it is necessary to enable JavaScript.
Here are the <a href="https://www.enable-javascript.com/" target="_blank"> instructions how to enable JavaScript in your web browser</a>.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `yarn dev`.
To run in production mode, use `yarn prod`.
-->
<script>
// Async load Google Material Icons, eliminate render-blocking resources.
// See https://github.com/typekit/webfontloader#get-started for details.
WebFontConfig = {
google: {
families: ['Material Icons']
}
};
(function (d) {
var wf = d.createElement('script'), s = d.scripts[0];
wf.src = 'https://cdnjs.cloudflare.com/ajax/libs/webfont/1.6.28/webfontloader.js';
wf.async = true;
s.parentNode.insertBefore(wf, s);
})(document);
</script>
</body>
</html>
================================================
FILE: frontend/public/locales/en/common.json
================================================
{
"header": {
"react": "React",
"dropdown": "Dropdown",
"notfound": "404"
},
"footer": {
"title": "Footer Content",
"content": "You can use rows and columns here to organize your footer content.",
"links": "Links",
"linkOne": "Link 1",
"linkTwo": "Link 2",
"linkThree": "Link 3",
"linkFour": "Link 4",
"moreLinks": "More Links"
},
"dropdown": {
"parallax": "Parallax"
}
}
================================================
FILE: frontend/public/locales/en/homePage.json
================================================
{
"title": "Home Page",
"carousel": {
"focusButtonText": "Focus me!",
"firstPanelTitle": "First Panel",
"secondPanelTitle": "Second Panel",
"thirdPanelTitle": "Third Panel",
"fourthPanelTitle": "Fourth Panel",
"firstPanelDescription": "This is your first panel, try to swipe it!",
"secondPanelDescription": "This is your second panel, try to swipe it!",
"thirdPanelDescription": "This is your third panel, try to swipe it!",
"fourthPanelDescription": "This is your fourth panel, try to swipe it!",
"tooltipText": "Click me! >. <",
"toastText": "I am a toast!"
},
"pushpin": {
"green": "Green",
"blue": "Blue",
"orange": "Orange",
"red": "Red",
"purple": "Purple",
"cyan": "Cyan",
"linkOne": "Link 1",
"linkTwo": "Link 2"
}
}
================================================
FILE: frontend/public/locales/en/notFoundPage.json
================================================
{
"title": "404 Page Not Found"
}
================================================
FILE: frontend/public/locales/en/parallaxPage.json
================================================
{
"title": "Parallax Page",
"description": "Parallax is an effect where the background content or image in this case, is moved at a different speed than the foreground content while scrolling."
}
================================================
FILE: frontend/public/locales/en/reactPage.json
================================================
{
"title": "React Page",
"fetchNote": {
"asyncCalls": "async calls",
"fetchAllNotes": "Fetch All Notes From DB"
},
"todoLayout": {
"title": "todos",
"todoFooter": {
"all": "All",
"active": "Active",
"completed": "Completed"
},
"todoInput": {
"addTodo": "Add todo"
}
}
}
================================================
FILE: frontend/public/locales/jp/common.json
================================================
{
"header": {
"react": "React",
"dropdown": "ドロップダウンリスト",
"notfound": "404"
},
"footer": {
"title": "フッターコンテンツ",
"content": "ここでは行と列を使用してフッターのコンテンツを整理できます。",
"links": "リンク",
"linkOne": "リンク 1",
"linkTwo": "リンク 2",
"linkThree": "リンク 3",
"linkFour": "リンク 4",
"moreLinks": "その他のリンク"
},
"dropdown": {
"parallax": "視差"
}
}
================================================
FILE: frontend/public/locales/jp/homePage.json
================================================
{
"title": "ホームページ",
"carousel": {
"focusButtonText": "フォーカス!",
"firstPanelTitle": "最初のパネル",
"secondPanelTitle": "第2のパネル",
"thirdPanelTitle": "第3のパネル",
"fourthPanelTitle": "第4のパネル",
"firstPanelDescription": "これはあなたの最初のパネルです、それをスワイプしよう!",
"secondPanelDescription": "これはあなたの第2のパネルです、それをスワイプしよう!",
"thirdPanelDescription": "これはあなたの第3のパネルです、それをスワイプしよう!",
"fourthPanelDescription": "これはあなたの第4のパネルです、それをスワイプしよう!",
"tooltipText": "クリック! >. <",
"toastText": "私はトーストです!"
},
"pushpin": {
"green": "緑",
"blue": "青",
"orange": "橙",
"red": "赤",
"purple": "紫",
"cyan": "シアン",
"linkOne": "リンク 1",
"linkTwo": "リンク 2"
}
}
================================================
FILE: frontend/public/locales/jp/notFoundPage.json
================================================
{
"title": "404 ページが見つかりません"
}
================================================
FILE: frontend/public/locales/jp/parallaxPage.json
================================================
{
"title": "視差ページ",
"description": "Parallax(パララックス)は、この場合のバックグラウンドコンテンツまたは画像がスクロール中にフォアグラウンドコンテンツとは異なる速度で移動される場合の効果です。"
}
================================================
FILE: frontend/public/locales/jp/reactPage.json
================================================
{
"title": "Reactページ",
"fetchNote": {
"asyncCalls": "非同期関数",
"fetchAllNotes": "DB内のメモを取得する"
},
"todoLayout": {
"title": "todoリスト",
"todoFooter": {
"all": "全部",
"active": "アクティブ",
"completed": "完成した"
},
"todoInput": {
"addTodo": "追加する"
}
}
}
================================================
FILE: frontend/public/locales/zh/common.json
================================================
{
"header": {
"react": "React",
"dropdown": "下拉列表",
"notfound": "404"
},
"footer": {
"title": "页脚内容",
"content": "你可以在这里使用行或者列来编辑的页脚内容.",
"links": "链接",
"linkOne": "链接 1",
"linkTwo": "链接 2",
"linkThree": "链接 3",
"linkFour": "链接 4",
"moreLinks": "更多链接"
},
"dropdown": {
"parallax": "视差"
}
}
================================================
FILE: frontend/public/locales/zh/homePage.json
================================================
{
"title": "首页",
"carousel": {
"focusButtonText": "看我看我!",
"firstPanelTitle": "第一个面板",
"secondPanelTitle": "第二个面板",
"thirdPanelTitle": "第三个面板",
"fourthPanelTitle": "第四个面板",
"firstPanelDescription": "这是你的第一个面板,试试左右滑动它!",
"secondPanelDescription": "这是你的第二个面板,试试左右滑动它!",
"thirdPanelDescription": "这是你的第三个面板,试试左右滑动它!",
"fourthPanelDescription": "这是你的第四个面板,试试左右滑动它!",
"tooltipText": "戳我! >. <",
"toastText": "哇我是一个toast!"
},
"pushpin": {
"green": "绿色",
"blue": "蓝色",
"orange": "橘色",
"red": "红色",
"purple": "紫色",
"cyan": "青色",
"linkOne": "链接 1",
"linkTwo": "链接 2"
}
}
================================================
FILE: frontend/public/locales/zh/notFoundPage.json
================================================
{
"title": "404 页面不存在"
}
================================================
FILE: frontend/public/locales/zh/parallaxPage.json
================================================
{
"title": "视差页面",
"description": "Parallax(视差) 是指一种背景内容或图像在滚动时以与前景内容不同的速度移动的效果。"
}
================================================
FILE: frontend/public/locales/zh/reactPage.json
================================================
{
"title": "React页面",
"fetchNote": {
"asyncCalls": "异步函数",
"fetchAllNotes": "获取数据库所有笔记"
},
"todoLayout": {
"title": "待办事项",
"todoFooter": {
"all": "全部",
"active": "待办",
"completed": "已完成"
},
"todoInput": {
"addTodo": "添加待办事项"
}
}
}
================================================
FILE: frontend/public/manifest.webmanifest
================================================
{
"name": "Boilerplate",
"short_name": "Boilerplate",
"start_url": ".",
"display": "standalone",
"orientation": "portrait",
"background_color": "#2196f3",
"theme_color": "#2196f3",
"description": "Full-stack boilerplate that using express with webpack, react and typescript!",
"icons": [
{
"src": "icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
================================================
FILE: frontend/public/robots.txt
================================================
User-agent: *
Disallow:
================================================
FILE: frontend/src/App.tsx
================================================
import { ConnectedRouter } from 'connected-react-router/immutable';
import { History } from 'history';
import React from 'react';
import { Provider } from 'react-redux';
import { Store } from 'redux';
import router from 'router';
import { IGlobalState } from 'types/global';
interface IAppProps {
store: Store<IGlobalState>;
history: History;
}
export default (props: IAppProps) => (
<Provider store={props.store}>
<ConnectedRouter history={props.history}>
{router}
</ConnectedRouter>
</Provider>
);
================================================
FILE: frontend/src/components/ContentLoader/__tests__/__snapshots__/contentLoader.spec.tsx.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BodyContentLoader should renders correctly 1`] = `
<div
className="bodyContentLoader"
>
<ContentLoader
height={200}
primaryColor="#f3f3f3"
secondaryColor="#d7d6d8"
speed={1}
width={400}
>
<rect
height="15.1"
rx="4"
ry="4"
width="76.63"
x="160.5"
y="11"
/>
<rect
height="151.36"
rx="1"
ry="1"
width="275.4"
x="60.5"
y="35.05"
/>
</ContentLoader>
</div>
`;
exports[`FooterContentLoader should renders correctly 1`] = `
<ContentLoader
height={400}
primaryColor="#f3f3f3"
secondaryColor="#d7d6d8"
speed={1}
width={2500}
>
<rect
height="400"
width="2500"
x="0"
y="0"
/>
</ContentLoader>
`;
exports[`HeaderContentLoader should renders correctly 1`] = `
<ContentLoader
height={95}
primaryColor="#f3f3f3"
secondaryColor="#d7d6d8"
speed={1}
width={2500}
>
<rect
height="95"
width="2500"
x="0"
y="0"
/>
</ContentLoader>
`;
================================================
FILE: frontend/src/components/ContentLoader/__tests__/contentLoader.spec.tsx
================================================
import React from 'react';
import ShallowRenderer from 'react-test-renderer/shallow';
import { BodyContentLoader, FooterContentLoader, HeaderContentLoader } from '../contentLoader';
describe('HeaderContentLoader', () => {
it('should renders correctly', () => {
const renderer = ShallowRenderer.createRenderer();
const result = renderer.render(
<HeaderContentLoader />,
);
expect(result).toMatchSnapshot();
renderer.unmount();
});
});
describe('BodyContentLoader', () => {
it('should renders correctly', () => {
const renderer = ShallowRenderer.createRenderer();
const result = renderer.render(
<BodyContentLoader />,
);
expect(result).toMatchSnapshot();
renderer.unmount();
});
});
describe('FooterContentLoader', () => {
it('should renders correctly', () => {
const renderer = ShallowRenderer.createRenderer();
const result = renderer.render(
<FooterContentLoader />,
);
expect(result).toMatchSnapshot();
renderer.unmount();
});
});
================================================
FILE: frontend/src/components/ContentLoader/contentLoader.scss
================================================
.bodyContentLoader {
margin: auto;
}
================================================
FILE: frontend/src/components/ContentLoader/contentLoader.tsx
================================================
import React from 'react';
import ContentLoader from 'react-content-loader';
const styles = require('./contentLoader.scss');
export const BodyContentLoader = () => (
<div className={styles.bodyContentLoader}>
<ContentLoader
height={200}
width={400}
speed={1}
primaryColor='#f3f3f3'
secondaryColor='#d7d6d8'
>
<rect x='160.5' y='11' rx='4' ry='4' width='76.63' height='15.1' />
<rect x='60.5' y='35.05' rx='1' ry='1' width='275.4' height='151.36' />
</ContentLoader>
</div>
);
export const HeaderContentLoader = () => (
<ContentLoader
height={95}
width={2500}
speed={1}
primaryColor='#f3f3f3'
secondaryColor='#d7d6d8'
>
<rect x='0' y='0' width='2500' height='95' />
</ContentLoader>
);
export const FooterContentLoader = () => (
<ContentLoader
height={400}
width={2500}
speed={1}
primaryColor='#f3f3f3'
secondaryColor='#d7d6d8'
>
<rect x='0' y='0' width='2500' height='400' />
</ContentLoader>
);
================================================
FILE: frontend/src/components/ContentLoader/index.tsx
================================================
export { BodyContentLoader, HeaderContentLoader, FooterContentLoader } from './contentLoader';
================================================
FILE: frontend/src/components/Dropdown/__tests__/__snapshots__/dropdown.spec.tsx.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Dropdown should renders correctly 1`] = `
Array [
<ul
className="dropdown-content"
id="id"
>
<li>
<a
href="/dropdown"
onClick={[Function]}
>
dropdown.dropdown
</a>
</li>
</ul>,
",",
]
`;
================================================
FILE: frontend/src/components/Dropdown/__tests__/dropdown.spec.tsx
================================================
import 'materialize-css';
import React, { Fragment } from 'react';
import { BrowserRouter } from 'react-router-dom';
import TestRenderer from 'react-test-renderer';
import { Dropdown } from '../dropdown';
describe('Dropdown', () => {
it('should renders correctly', () => {
const renderer = TestRenderer.create(
<BrowserRouter>
<Fragment>
<Dropdown id='id' dropdownLists={['dropdown']} />,
</Fragment>
</BrowserRouter>,
);
expect(renderer).toMatchSnapshot();
renderer.unmount();
});
});
================================================
FILE: frontend/src/components/Dropdown/dropdown.tsx
================================================
import React from 'react';
import { Translation } from 'react-i18next';
import { Link } from 'react-router-dom';
interface IDropdownProps {
id: string;
dropdownLists: string[];
}
const dropdownConfig: Partial<M.DropdownOptions> = {
coverTrigger: false,
};
export class Dropdown extends React.Component<IDropdownProps> {
public componentDidMount() {
const elems = document.querySelectorAll('.dropdown-button');
M.Dropdown.init(elems, dropdownConfig);
}
public render() {
return (
<Translation ns='common'>
{(t) => (
<ul id={this.props.id} className='dropdown-content'>
{this.props.dropdownLists.map((key) =>
<li key={key}><Link to={`/${key}`}>{t('dropdown.' + key)}</Link></li>,
)}
</ul>
)}
</Translation>
);
}
}
export default Dropdown;
================================================
FILE: frontend/src/components/Dropdown/index.tsx
================================================
export { default } from './dropdown';
================================================
FILE: frontend/src/components/Footer/__tests__/__snapshots__/footer.spec.tsx.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Footer should renders correctly 1`] = `
<footer
className="page-footer"
>
<div
className="container"
>
<div
className="row"
>
<div
className="col l6 s12"
>
<h5
className="white-text"
>
footer.title
</h5>
<p
className="grey-text text-lighten-4"
>
footer.content
</p>
</div>
<div
className="col l3 offset-l3 s12"
>
<h5
className="white-text"
>
footer.links
</h5>
<ul>
<li>
<a
className="grey-text text-lighten-3"
href="#"
>
footer.linkOne
</a>
</li>
<li>
<a
className="grey-text text-lighten-3"
href="#"
>
footer.linkTwo
</a>
</li>
<li>
<a
className="grey-text text-lighten-3"
href="#"
>
footer.linkThree
</a>
</li>
<li>
<a
className="grey-text text-lighten-3"
href="#"
>
footer.linkFour
</a>
</li>
</ul>
</div>
</div>
</div>
<div
className="footer-copyright"
>
<div
className="container"
>
© 2017 Copyright
<a
className="grey-text text-lighten-4"
href="https://github.com/Armour"
>
Armour
</a>
<a
className="grey-text text-lighten-4 right"
href="#"
>
footer.moreLinks
</a>
</div>
</div>
</footer>
`;
================================================
FILE: frontend/src/components/Footer/__tests__/footer.spec.tsx
================================================
import React from 'react';
import TestRenderer from 'react-test-renderer';
import { Footer } from '../footer';
describe('Footer', () => {
it('should renders correctly', () => {
const renderer = TestRenderer.create(
<Footer />,
);
expect(renderer).toMatchSnapshot();
renderer.unmount();
});
});
================================================
FILE: frontend/src/components/Footer/footer.tsx
================================================
import React from 'react';
import { Translation } from 'react-i18next';
export class Footer extends React.Component {
public render() {
return (
<Translation ns='common'>
{(t) => (
<footer className='page-footer'>
<div className='container'>
<div className='row'>
<div className='col l6 s12'>
<h5 className='white-text'>{t('footer.title')}</h5>
<p className='grey-text text-lighten-4'>{t('footer.content')}</p>
</div>
<div className='col l3 offset-l3 s12'>
<h5 className='white-text'>{t('footer.links')}</h5>
<ul>
<li><a className='grey-text text-lighten-3' href='#'>{t('footer.linkOne')}</a></li>
<li><a className='grey-text text-lighten-3' href='#'>{t('footer.linkTwo')}</a></li>
<li><a className='grey-text text-lighten-3' href='#'>{t('footer.linkThree')}</a></li>
<li><a className='grey-text text-lighten-3' href='#'>{t('footer.linkFour')}</a></li>
</ul>
</div>
</div>
</div>
<div className='footer-copyright'>
<div className='container'>
© 2017 Copyright <a className='grey-text text-lighten-4' href='https://github.com/Armour'>Armour</a>
<a className='grey-text text-lighten-4 right' href='#'>{t('footer.moreLinks')}</a>
</div>
</div>
</footer>
)}
</Translation>
);
}
}
export default Footer;
================================================
FILE: frontend/src/components/Footer/index.tsx
================================================
export { default } from './footer';
================================================
FILE: frontend/src/components/Header/__tests__/__snapshots__/header.spec.tsx.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Header should renders correctly 1`] = `
Array [
<div>
<nav>
<div
className="nav-wrapper"
>
<div
className="container"
>
<a
className="brand-logo"
href="/"
onClick={[Function]}
>
<img
alt="logo"
className="logo"
src={
Object {
"default": "test-file-stub",
}
}
/>
</a>
<a
className="sidenav-trigger"
data-target="nav-mobile"
href="#"
>
<i
className="material-icons"
>
menu
</i>
</a>
<ul
className="right hide-on-med-and-down"
id="nav-desktop"
>
<li
className=""
>
<a
href="/react"
onClick={[Function]}
>
header.react
</a>
</li>
<li
className="active"
>
<a
className="dropdown-button"
data-target="header-dropdown-desktop"
href="#"
>
header.dropdown
<i
className="material-icons right"
>
arrow_drop_down
</i>
</a>
</li>
<li
className=""
>
<a
href="/404"
onClick={[Function]}
>
header.notfound
</a>
</li>
<Dropdown
dropdownLists={
Array [
"parallax",
]
}
id="header-dropdown-desktop"
/>
</ul>
</div>
</div>
</nav>
<ul
className="sidenav"
id="nav-mobile"
>
<li
className=""
>
<a
href="/react"
onClick={[Function]}
>
header.react
</a>
</li>
<li
className="active"
>
<a
className="dropdown-button"
data-target="header-dropdown-mobile"
href="#"
>
header.dropdown
<i
className="material-icons right"
>
arrow_drop_down
</i>
</a>
</li>
<li
className=""
>
<a
href="/404"
onClick={[Function]}
>
header.notfound
</a>
</li>
<Dropdown
dropdownLists={
Array [
"parallax",
]
}
id="header-dropdown-mobile"
/>
</ul>
</div>,
",",
]
`;
================================================
FILE: frontend/src/components/Header/__tests__/header.spec.tsx
================================================
import 'materialize-css';
import React, { Fragment } from 'react';
import { BrowserRouter } from 'react-router-dom';
import TestRenderer from 'react-test-renderer';
import { Header } from '../header';
jest.mock('components/Dropdown', () => 'Dropdown');
describe('Header', () => {
it('should renders correctly', () => {
const renderer = TestRenderer.create(
<BrowserRouter>
<Fragment>
<Header pathname='/parallax' />,
</Fragment>
</BrowserRouter>,
);
expect(renderer).toMatchSnapshot();
renderer.unmount();
});
});
================================================
FILE: frontend/src/components/Header/header.scss
================================================
.logo {
margin: 6px 0 0 0;
max-height: 50px;
}
================================================
FILE: frontend/src/components/Header/header.tsx
================================================
import React from 'react';
import { Translation } from 'react-i18next';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import Dropdown from 'components/Dropdown';
import { IGlobalStateRecord } from 'types/global';
const styles = require('./header.scss');
// Component
interface IHeaderStateProps {
pathname: string;
}
export class Header extends React.Component<IHeaderStateProps> {
constructor(props: IHeaderStateProps) {
super(props);
}
public checkActive(urls: string[]) {
let active = false;
urls.forEach((url) => {
if (this.props.pathname === '/' + url) {
active = true;
}
});
return active ? 'active' : '';
}
public componentDidMount() {
const elems = document.querySelectorAll('.sidenav');
M.Sidenav.init(elems);
}
public render() {
const dropdownList = ['parallax'];
return (
<Translation ns='common'>
{(t) => (
<div>
<nav>
<div className='nav-wrapper'>
<div className='container'>
<Link className='brand-logo' to='/'><img className={styles.logo} alt='logo' src={require('./assets/images/logo.png')}/></Link>
<a href='#' data-target='nav-mobile' className='sidenav-trigger'>
<i className='material-icons'>menu</i>
</a>
<ul id='nav-desktop' className='right hide-on-med-and-down'>
<li className={this.checkActive(['react'])} key='react'><Link to='/react'>{t('header.react')}</Link></li>
<li className={this.checkActive(dropdownList)} key='materialize'>
<a className='dropdown-button' href='#' data-target='header-dropdown-desktop'>{t('header.dropdown')}<i className='material-icons right'>arrow_drop_down</i></a>
</li>
<li className={this.checkActive(['404'])} key='404'><Link to='/404'>{t('header.notfound')}</Link></li>
<Dropdown id='header-dropdown-desktop' dropdownLists={dropdownList} />
</ul>
</div>
</div>
</nav>
<ul id='nav-mobile' className='sidenav'>
<li className={this.checkActive(['react'])} key='react'><Link to='/react'>{t('header.react')}</Link></li>
<li className={this.checkActive(dropdownList)} key='materialize'>
<a className='dropdown-button' href='#' data-target='header-dropdown-mobile'>{t('header.dropdown')}<i className='material-icons right'>arrow_drop_down</i></a>
</li>
<li className={this.checkActive(['404'])} key='404'><Link to='/404'>{t('header.notfound')}</Link></li>
<Dropdown id='header-dropdown-mobile' dropdownLists={dropdownList} />
</ul>
</div>
)}
</Translation>
);
}
}
// Container
const mapStateToProps = (state: IGlobalStateRecord): IHeaderStateProps => ({
pathname: state.getIn(['router', 'location', 'pathname']),
});
export default connect(
mapStateToProps,
null,
)(Header);
================================================
FILE: frontend/src/components/Header/index.tsx
================================================
export { default } from './header';
================================================
FILE: frontend/src/i18n/index.tsx
================================================
import i18n from 'i18next';
import detector from 'i18next-browser-languagedetector';
import backend from 'i18next-xhr-backend';
import { initReactI18next } from 'react-i18next';
import { isProduction } from 'utils';
export default i18n
.use(detector)
.use(backend)
.use(initReactI18next)
.init({
debug: !isProduction,
backend: {
loadPath: './locales/{{lng}}/{{ns}}.json',
},
whitelist: ['en', 'zh', 'jp'],
fallbackLng: 'en',
fallbackNS: 'common',
interpolation: {
escapeValue: false, // not needed for react.
},
react: {
wait: false,
},
}, (err, _) => {
if (err) {
return console.error('Load i18n instance failed.', err);
}
});
================================================
FILE: frontend/src/index.tsx
================================================
import { createBrowserHistory } from 'history';
import OfflinePluginRuntime from 'offline-plugin/runtime';
import React from 'react';
import ReactDom from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import 'i18n';
import App from 'App';
import { Map } from 'immutable';
import configureStore from 'store';
import { isProduction } from 'utils';
// Webpack offline plugin
if (isProduction) {
OfflinePluginRuntime.install();
}
// To keep reducers self-sufficient and reusable, we choose to not set
// initial state here, and let each reducer to handle the default state
// https://github.com/reactjs/redux/issues/1189#issuecomment-168025590
const initialState = Map();
// Create browser history
const history = createBrowserHistory();
// Configure store
const store = configureStore(history, initialState);
// Create render function
const render = (Component: any) => {
ReactDom.render(
<AppContainer>
<Component store={store} history={history} />
</AppContainer>,
document.getElementById('root'),
);
};
// First time render
render(App);
// Hot Reload Module API
if (module.hot) {
module.hot.accept('./App', () => {
const NextApp = require('App').default;
render(NextApp);
});
}
================================================
FILE: frontend/src/pages/HomePage/__tests__/__snapshots__/homePage.spec.tsx.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`HomePage should renders correctly 1`] = `
Array [
<div
className="home-page-block"
>
<h1
className="page-title"
>
title
</h1>
<div
className="container"
>
<Carousel />
</div>
</div>,
<Pushpin
color="blue"
depth="lighten-1"
/>,
<Pushpin
color="green"
depth="lighten-1"
/>,
<Pushpin
color="orange"
depth="lighten-1"
/>,
<Pushpin
color="red"
depth="lighten-1"
/>,
<Pushpin
color="purple"
depth="lighten-1"
/>,
<Pushpin
color="cyan"
depth="lighten-1"
/>,
<TranslationButton />,
]
`;
================================================
FILE: frontend/src/pages/HomePage/__tests__/homePage.spec.tsx
================================================
import 'materialize-css';
import React from 'react';
import TestRenderer from 'react-test-renderer';
import { HomePage } from '../homePage';
jest.mock('../components/Carousel', () => 'Carousel');
jest.mock('../components/Pushpin', () => 'Pushpin');
jest.mock('../components/TranslationButton', () => 'TranslationButton');
describe('HomePage', () => {
it('should renders correctly', () => {
const renderer = TestRenderer.create(
<HomePage />,
);
expect(renderer).toMatchSnapshot();
renderer.unmount();
});
});
================================================
FILE: frontend/src/pages/HomePage/components/Carousel/__tests__/__snapshots__/carousel.spec.tsx.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Carousel should renders correctly 1`] = `
<div
className="carousel carousel-slider center z-depth-3"
data-indicators="true"
>
<div
className="carousel-fixed-item center"
>
<a
className="btn tooltipped waves-effect white grey-text text-darken-2"
onClick={[Function]}
role="button"
>
carousel.focusButtonText
</a>
</div>
<a
className="carousel-item green white-text"
href="#one!"
>
<h2>
carousel.firstPanelTitle
</h2>
<p>
carousel.firstPanelDescription
</p>
</a>
<a
className="carousel-item amber white-text"
href="#two!"
>
<h2>
carousel.secondPanelTitle
</h2>
<p>
carousel.secondPanelDescription
</p>
</a>
<a
className="carousel-item red white-text"
href="#three!"
>
<h2>
carousel.thirdPanelTitle
</h2>
<p>
carousel.thirdPanelDescription
</p>
</a>
<a
className="carousel-item purple white-text"
href="#four!"
>
<h2>
carousel.fourthPanelTitle
</h2>
<p>
carousel.fourthPanelDescription
</p>
</a>
</div>
`;
================================================
FILE: frontend/src/pages/HomePage/components/Carousel/__tests__/carousel.spec.tsx
================================================
import 'materialize-css';
import React from 'react';
import TestRenderer from 'react-test-renderer';
import { Carousel } from '../carousel';
describe('Carousel', () => {
it('should renders correctly', () => {
const renderer = TestRenderer.create(
<Carousel />,
);
expect(renderer).toMatchSnapshot();
renderer.unmount();
});
});
================================================
FILE: frontend/src/pages/HomePage/components/Carousel/carousel.tsx
================================================
import React from 'react';
import { Translation } from 'react-i18next';
import { CAROUSEL_AUTOPLAY_INTERVAL, TOAST_DISPLAY_DURATION, TOOLTIP_DELAY_TIME } from './constants/carousel';
const tooltipConfig: Partial<M.TooltipOptions> = {
enterDelay: TOOLTIP_DELAY_TIME,
position: 'top',
};
const carouselConfig: Partial<M.CarouselOptions> = {
fullWidth: true,
indicators: true,
};
const toastConfig: Partial<M.ToastOptions> = {
inDuration: TOAST_DISPLAY_DURATION / 4,
outDuration: TOAST_DISPLAY_DURATION / 4,
displayLength: TOAST_DISPLAY_DURATION,
};
export class Carousel extends React.Component {
public timer: number = 0;
public componentDidMount() {
const carouselElems = document.querySelectorAll('.carousel.carousel-slider');
const carousels = M.Carousel.init(carouselElems, carouselConfig);
this.timer = window.setTimeout(() => this.autoPlayCarousel(carousels), CAROUSEL_AUTOPLAY_INTERVAL);
}
public componentWillUnmount() {
clearTimeout(this.timer);
}
public autoPlayCarousel = (carousels: M.Carousel[]) => {
carousels.forEach((carousel) => {
if (!carousel.pressed) {
carousel.next();
}
});
this.timer = window.setTimeout(() => this.autoPlayCarousel(carousels), CAROUSEL_AUTOPLAY_INTERVAL);
}
public displayToast = (text: string) => (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
toastConfig.html = text;
M.toast(toastConfig);
}
public initTooltip = (text: string) => {
tooltipConfig.html = text;
const tooltipElems = document.querySelectorAll('.tooltipped');
M.Tooltip.init(tooltipElems, tooltipConfig);
}
public render() {
return (
<Translation ns='homePage'>
{(t) => (
this.initTooltip(t('carousel.tooltipText')),
<div className='carousel carousel-slider center z-depth-3' data-indicators='true'>
<div className='carousel-fixed-item center'>
<a className='btn tooltipped waves-effect white grey-text text-darken-2' onClick={this.displayToast(t('carousel.toastText'))} role='button'>
{t('carousel.focusButtonText')}
</a>
</div>
<a className='carousel-item green white-text' href='#one!'>
<h2>{t('carousel.firstPanelTitle')}</h2>
<p>{t('carousel.firstPanelDescription')}</p>
</a>
<a className='carousel-item amber white-text' href='#two!'>
<h2>{t('carousel.secondPanelTitle')}</h2>
<p>{t('carousel.secondPanelDescription')}</p>
</a>
<a className='carousel-item red white-text' href='#three!'>
<h2>{t('carousel.thirdPanelTitle')}</h2>
<p>{t('carousel.thirdPanelDescription')}</p>
</a>
<a className='carousel-item purple white-text' href='#four!'>
<h2>{t('carousel.fourthPanelTitle')}</h2>
<p>{t('carousel.fourthPanelDescription')}</p>
</a>
</div>
)}
</Translation>
);
}
}
export default Carousel;
================================================
FILE: frontend/src/pages/HomePage/components/Carousel/constants/carousel.tsx
================================================
// Toast timer
export const TOAST_DISPLAY_DURATION = 3000;
// Tooltip timer
export const TOOLTIP_DELAY_TIME = 50;
// Carousel autoplay interval
export const CAROUSEL_AUTOPLAY_INTERVAL = 3500;
================================================
FILE: frontend/src/pages/HomePage/components/Carousel/index.tsx
================================================
export { default } from './carousel';
================================================
FILE: frontend/src/pages/HomePage/components/Pushpin/__tests__/__snapshots__/pushpin.spec.tsx.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Pushpin should renders correctly 1`] = `
<div
className="pushpin red base"
id="red"
>
<nav
className="pushpin-nav pin-top"
data-target="pushpin-red"
>
<div
className="nav-wrapper red pushpin-red"
>
<div
className="container"
>
<a
className="brand-logo"
href="#"
>
pushpin.red
</a>
<ul
className="right hide-on-med-and-down"
id="nav-demo-red"
>
<li>
<a
href="#"
>
pushpin.linkOne
</a>
</li>
<li>
<a
href="#"
>
pushpin.linkTwo
</a>
</li>
</ul>
</div>
</div>
</nav>
</div>
`;
================================================
FILE: frontend/src/pages/HomePage/components/Pushpin/__tests__/pushpin.spec.tsx
================================================
import React from 'react';
import TestRenderer from 'react-test-renderer';
import { Pushpin } from '../pushpin';
describe('Pushpin', () => {
it('should renders correctly', () => {
const renderer = TestRenderer.create(
<Pushpin color='red' depth='base' />,
);
expect(renderer).toMatchSnapshot();
renderer.unmount();
});
});
================================================
FILE: frontend/src/pages/HomePage/components/Pushpin/index.tsx
================================================
export { default } from './pushpin';
================================================
FILE: frontend/src/pages/HomePage/components/Pushpin/pushpin.scss
================================================
.pushpin {
height: 825px;
}
================================================
FILE: frontend/src/pages/HomePage/components/Pushpin/pushpin.tsx
================================================
import React from 'react';
import { Translation } from 'react-i18next';
const styles = require('./pushpin.scss');
interface IPushpinProps {
color: string;
depth: string;
}
export class Pushpin extends React.Component<IPushpinProps> {
public render() {
return (
<Translation ns='homePage'>
{(t) => (
<div id={this.props.color} className={`${styles.pushpin} ${this.props.color} ${this.props.depth}`}>
<nav className='pushpin-nav pin-top' data-target={'pushpin-' + this.props.color}>
<div className={`nav-wrapper ${this.props.color} pushpin-${this.props.color}`}>
<div className='container'>
<a href='#' className='brand-logo'>{t('pushpin.' + this.props.color)}</a>
<ul id={`nav-demo-${this.props.color}`} className='right hide-on-med-and-down'>
<li><a href='#'>{t('pushpin.linkOne')}</a></li>
<li><a href='#'>{t('pushpin.linkTwo')}</a></li>
</ul>
</div>
</div>
</nav>
</div>
)}
</Translation>
);
}
}
export default Pushpin;
================================================
FILE: frontend/src/pages/HomePage/components/TranslationButton/__tests__/__snapshots__/translationButton.spec.tsx.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TranslationButton should renders correctly 1`] = `
<div
className="fixed-action-btn vertical"
>
<a
className="btn-floating btn-large red"
>
<i
className="large material-icons"
>
translate
</i>
</a>
<ul>
<li>
<a
className="btn-floating red"
onClick={[Function]}
>
en
</a>
</li>
<li>
<a
className="btn-floating green"
onClick={[Function]}
>
zh
</a>
</li>
<li>
<a
className="btn-floating yellow darken-1"
onClick={[Function]}
>
jp
</a>
</li>
</ul>
</div>
`;
================================================
FILE: frontend/src/pages/HomePage/components/TranslationButton/__tests__/translationButton.spec.tsx
================================================
import 'materialize-css';
import React from 'react';
import TestRenderer from 'react-test-renderer';
import { TranslationButton } from '../translationButton';
describe('TranslationButton', () => {
it('should renders correctly', () => {
const renderer = TestRenderer.create(
<TranslationButton />,
);
expect(renderer).toMatchSnapshot();
renderer.unmount();
});
});
================================================
FILE: frontend/src/pages/HomePage/components/TranslationButton/index.tsx
================================================
export { default } from './translationButton';
================================================
FILE: frontend/src/pages/HomePage/components/TranslationButton/translationButton.tsx
================================================
import i18next from 'i18next';
import React from 'react';
import { Translation } from 'react-i18next';
const floatingActionButtonConfig: Partial<M.FloatingActionButtonOptions> = {
direction: 'top',
};
export class TranslationButton extends React.Component {
public componentDidMount() {
const elems = document.querySelectorAll('.fixed-action-btn');
M.FloatingActionButton.init(elems, floatingActionButtonConfig);
}
public changeLanguage = (i18n: i18next.i18n, lng: string) => (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
i18n.changeLanguage(lng);
}
public render() {
return (
<Translation>
{(_, {i18n}) => (
<div className='fixed-action-btn vertical'>
<a className='btn-floating btn-large red'>
<i className='large material-icons'>translate</i>
</a>
<ul>
<li><a className='btn-floating red' onClick={this.changeLanguage(i18n, 'en')}>en</a></li>
<li><a className='btn-floating green' onClick={this.changeLanguage(i18n, 'zh')}>zh</a></li>
<li><a className='btn-floating yellow darken-1' onClick={this.changeLanguage(i18n, 'jp')}>jp</a></li>
</ul>
</div>
)}
</Translation>
);
}
}
export default TranslationButton;
================================================
FILE: frontend/src/pages/HomePage/homePage.scss
================================================
.home-page-block {
height: 820px;
}
================================================
FILE: frontend/src/pages/HomePage/homePage.tsx
================================================
import React, { Fragment } from 'react';
import { Translation } from 'react-i18next';
import Carousel from './components/Carousel';
import Pushpin from './components/Pushpin';
import TranslationButton from './components/TranslationButton';
const styles = require('./homePage.scss');
export class HomePage extends React.Component {
public componentDidMount() {
document.querySelectorAll('.pushpin-nav').forEach((elem, _) => {
const target = document.querySelector('.' + elem.getAttribute('data-target')!);
const rect = target!.getBoundingClientRect();
let top = rect.top;
// Make sure element is not hidden (display: none) or disconnected
if (rect.width || rect.height || target!.getClientRects().length) {
top += window.pageYOffset - target!.ownerDocument!.documentElement!.clientTop;
}
const bottom = top + elem.parentElement!.getBoundingClientRect().height - rect.height;
M.Pushpin.init(elem, {
top,
bottom,
});
});
}
public render() {
return (
<Translation ns='homePage'>
{(t) => (
<Fragment>
<div className={styles['home-page-block']}>
<h1 className='page-title'>{t('title')}</h1>
<div className='container'>
<Carousel />
</div>
</div>
<Pushpin color='blue' depth='lighten-1' />
<Pushpin color='green' depth='lighten-1' />
<Pushpin color='orange' depth='lighten-1' />
<Pushpin color='red' depth='lighten-1' />
<Pushpin color='purple' depth='lighten-1' />
<Pushpin color='cyan' depth='lighten-1' />
<TranslationButton />
</Fragment>
)}
</Translation>
);
}
}
export default HomePage;
================================================
FILE: frontend/src/pages/HomePage/index.tsx
================================================
export { default } from './homePage';
================================================
FILE: frontend/src/pages/NotFoundPage/__tests__/__snapshots__/notFoundPage.spec.tsx.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`NotFoundPage should renders correctly 1`] = `
Array [
<h1
className="page-title"
>
title
</h1>,
<img
alt="not-found-img"
className="not-found-img"
height="550px"
src={
Object {
"default": "test-file-stub",
}
}
width="750px"
/>,
]
`;
================================================
FILE: frontend/src/pages/NotFoundPage/__tests__/notFoundPage.spec.tsx
================================================
import React from 'react';
import TestRenderer from 'react-test-renderer';
import { NotFoundPage } from '../notFoundPage';
describe('NotFoundPage', () => {
it('should renders correctly', () => {
const renderer = TestRenderer.create(
<NotFoundPage />,
);
expect(renderer).toMatchSnapshot();
renderer.unmount();
});
});
================================================
FILE: frontend/src/pages/NotFoundPage/index.tsx
================================================
export { default } from './notFoundPage';
================================================
FILE: frontend/src/pages/NotFoundPage/notFoundPage.scss
================================================
@import 'sass/variables';
.not-found-img {
display: block;
margin: auto;
padding: 60px;
max-width: 90%;
object-fit: cover;
}
@media only screen and (max-width: $small-screen) {
.not-found-img {
height: 360px;
max-width: 100%;
}
}
================================================
FILE: frontend/src/pages/NotFoundPage/notFoundPage.tsx
================================================
import React, { Fragment } from 'react';
import { Translation } from 'react-i18next';
const styles = require('./notFoundPage.scss');
const notFoundImageList = [
'404.gif',
'404-1.jpeg',
'404-2.jpeg',
'404.jpg',
];
interface INotFoundPageState {
imageId: number;
}
export class NotFoundPage extends React.Component<{}, INotFoundPageState> {
constructor(props: {}) {
super(props);
this.state = { imageId: this.getRandomInt(0, notFoundImageList.length) };
}
public getRandomInt = (min: number, max: number) => {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min; // The maximum is exclusive and the minimum is inclusive
}
public render() {
return (
<Translation ns='notFoundPage'>
{(t) => (
<Fragment>
<h1 className='page-title'>{t('title')}</h1>
<img className={styles['not-found-img']} src={require('./assets/images/' + notFoundImageList[this.state.imageId])} alt='not-found-img' height='550px' width='750px' />
</Fragment>
)}
</Translation>
);
}
}
export default NotFoundPage;
================================================
FILE: frontend/src/pages/ParallaxPage/__tests__/__snapshots__/parallaxPage.spec.tsx.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ParallaxPage should renders correctly 1`] = `
Array [
<div
className="white"
>
<h1
className="page-title"
>
title
</h1>
</div>,
<div
className="parallax-container"
>
<div
className="parallax"
>
<img
alt="parallax-img"
className="parallax-img"
src={
Object {
"default": "test-file-stub",
}
}
/>
</div>
</div>,
<div
className="section white"
>
<div
className="row container"
>
<h2
className="parallax-header"
>
Parallax
</h2>
<p
className="grey-text text-darken-3"
>
description
</p>
</div>
<div
className="row container"
>
<h4
className="light"
>
Parallax Demo Code
</h4>
<PrismCodes
language="language-tsx"
/>
</div>
</div>,
<div
className="parallax-container"
>
<div
className="parallax"
>
<img
alt="parallax-img"
className="parallax-img"
src={
Object {
"default": "test-file-stub",
}
}
/>
</div>
</div>,
]
`;
================================================
FILE: frontend/src/pages/ParallaxPage/__tests__/parallaxPage.spec.tsx
================================================
import 'materialize-css';
import React from 'react';
import TestRenderer from 'react-test-renderer';
import { ParallaxPage } from '../parallaxPage';
jest.mock('../components/PrismCodes', () => 'PrismCodes');
describe('ParallaxPage', () => {
it('should renders correctly', () => {
const renderer = TestRenderer.create(
<ParallaxPage />,
);
expect(renderer).toMatchSnapshot();
renderer.unmount();
});
});
================================================
FILE: frontend/src/pages/ParallaxPage/components/PrismCodes/__tests__/__snapshots__/prismCodes.spec.tsx.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PrismCodes component 1`] = `
<pre
className="language-tsx"
>
<code
className="col s12 language-tsx"
>
<div>
<div className='white'>
<h1 className='page-title'>{t('title')}</h1>
</div>
<div className='parallax-container'>
<div className='parallax'>
<img className='parallax-img' src={require('./assets/images/parallax1.jpg')} alt='parallax-img' />
</div>
</div>
<div className='section white'>
<div className='row container'>
<h2 className={styles['parallax-header']}>Parallax</h2>
<p className='grey-text text-darken-3'>{t('description')}</p>
</div>
<div className='row container'>
<h4 className='light'>Parallax Demo Code</h4>
<PrismCodes language='language-tsx'>
{PARALLAX_CODE}
</PrismCodes>
</div>
</div>
<div className='parallax-container'>
<div className='parallax'>
<img className='parallax-img' src={require('./assets/images/parallax2.jpg')} alt='parallax-img' />
</div>
</div>
</div>
</code>
</pre>
`;
================================================
FILE: frontend/src/pages/ParallaxPage/components/PrismCodes/__tests__/prismCodes.spec.tsx
================================================
import React from 'react';
import renderer from 'react-test-renderer';
import { PARALLAX_CODE } from '../constants/prismCodes';
import PrismCodes from '../prismCodes';
jest.mock('prismjs', () => ({
highlightAll: () => { return; },
}));
test('PrismCodes component', () => {
const component = renderer.create(
<PrismCodes language='language-tsx'>
{PARALLAX_CODE}
</PrismCodes>,
);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
component.unmount();
});
================================================
FILE: frontend/src/pages/ParallaxPage/components/PrismCodes/constants/prismCodes.tsx
================================================
// Code snippets
export const PARALLAX_CODE = `
<div>
<div className='white'>
<h1 className='page-title'>{t('title')}</h1>
</div>
<div className='parallax-container'>
<div className='parallax'>
<img className='parallax-img' src={require('./assets/images/parallax1.jpg')} alt='parallax-img' />
</div>
</div>
<div className='section white'>
<div className='row container'>
<h2 className={styles['parallax-header']}>Parallax</h2>
<p className='grey-text text-darken-3'>{t('description')}</p>
</div>
<div className='row container'>
<h4 className='light'>Parallax Demo Code</h4>
<PrismCodes language='language-tsx'>
{PARALLAX_CODE}
</PrismCodes>
</div>
</div>
<div className='parallax-container'>
<div className='parallax'>
<img className='parallax-img' src={require('./assets/images/parallax2.jpg')} alt='parallax-img' />
</div>
</div>
</div>
`;
================================================
FILE: frontend/src/pages/ParallaxPage/components/PrismCodes/index.tsx
================================================
export { default } from './prismCodes';
export * from './constants/prismCodes';
================================================
FILE: frontend/src/pages/ParallaxPage/components/PrismCodes/prismCodes.scss
================================================
code,
pre {
position: relative;
font-size: 1em;
}
pre[class*="language-"] {
padding: 20px 22px;
border: solid 1px rgba(51, 51, 51, 0.12);
}
pre[class*="language-"]::before {
position: absolute;
padding: 1px 5px;
background: #e8e6e3;
top: 0;
left: 0;
font-family: "Roboto", sans-serif;
-webkit-font-smoothing: antialiased;
color: #555;
content: attr(class);
font-size: 0.9em;
border: solid 1px rgba(51, 51, 51, 0.12);
border-top: none;
border-left: none;
}
================================================
FILE: frontend/src/pages/ParallaxPage/components/PrismCodes/prismCodes.tsx
================================================
import React from 'react';
import { highlightAll } from 'prismjs';
import 'prismjs/themes/prism.css';
import './prismCodes.scss';
interface IPrismCodesProps {
language: string;
}
export default class PrismCodes extends React.Component<IPrismCodesProps> {
public componentDidMount() {
highlightAll();
}
public render() {
return (
<pre className={this.props.language}>
<code className={`col s12 ${this.props.language}`}>
{this.props.children}
</code>
</pre>
);
}
}
================================================
FILE: frontend/src/pages/ParallaxPage/index.tsx
================================================
export { default } from './parallaxPage';
================================================
FILE: frontend/src/pages/ParallaxPage/parallaxPage.scss
================================================
@import 'sass/variables';
.parallax-header {
color: $primary-color;
font-weight: 300;
}
================================================
FILE: frontend/src/pages/ParallaxPage/parallaxPage.tsx
================================================
import React, { Fragment } from 'react';
import { Translation } from 'react-i18next';
import PrismCodes, { PARALLAX_CODE } from './components/PrismCodes';
const styles = require('./parallaxPage.scss');
export class ParallaxPage extends React.Component {
public componentDidMount() {
const elems = document.querySelectorAll('.parallax');
M.Parallax.init(elems);
}
public render() {
return (
<Translation ns='parallaxPage'>
{(t) => (
<Fragment>
<div className='white'>
<h1 className='page-title'>{t('title')}</h1>
</div>
<div className='parallax-container'>
<div className='parallax'>
<img className='parallax-img' src={require('./assets/images/parallax1.jpg')} alt='parallax-img' />
</div>
</div>
<div className='section white'>
<div className='row container'>
<h2 className={styles['parallax-header']}>Parallax</h2>
<p className='grey-text text-darken-3'>{t('description')}</p>
</div>
<div className='row container'>
<h4 className='light'>Parallax Demo Code</h4>
<PrismCodes language='language-tsx'>
{PARALLAX_CODE}
</PrismCodes>
</div>
</div>
<div className='parallax-container'>
<div className='parallax'>
<img className='parallax-img' src={require('./assets/images/parallax2.jpg')} alt='parallax-img' />
</div>
</div>
</Fragment>
)}
</Translation>
);
}
}
export default ParallaxPage;
================================================
FILE: frontend/src/pages/ReactPage/__tests__/__snapshots__/reactPage.spec.tsx.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ReactPage should renders correctly 1`] = `
<div>
<div
className="react-container"
>
<h1
className="page-title"
>
title
</h1>
<TodoLayout />
<FetchNote />
</div>
</div>
`;
================================================
FILE: frontend/src/pages/ReactPage/__tests__/reactPage.spec.tsx
================================================
import React from 'react';
import TestRenderer from 'react-test-renderer';
import { ReactPage } from '../reactPage';
jest.mock('../components/FetchNote', () => 'FetchNote');
jest.mock('../components/TodoLayout', () => 'TodoLayout');
describe('ReactPage', () => {
it('should renders correctly', () => {
const renderer = TestRenderer.create(
<ReactPage />,
);
expect(renderer).toMatchSnapshot();
renderer.unmount();
});
});
================================================
FILE: frontend/src/pages/ReactPage/components/FetchNote/__tests__/__snapshots__/fetchNote.spec.tsx.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FetchNote should renders correctly 1`] = `
<div
className="center-align z-depth-2 fetch-note-layout"
>
<span
className="fetch-note-title"
>
fetchNote.asyncCalls
</span>
<div
className="fetchAllNotes"
>
<a
className="btn waves-effect fetch-note-filter-btn"
onClick={[Function]}
role="button"
>
fetchNote.fetchAllNotes
</a>
</div>
</div>
`;
================================================
FILE: frontend/src/pages/ReactPage/components/FetchNote/__tests__/fetchNote.spec.tsx
================================================
import { List } from 'immutable';
import 'materialize-css';
import React from 'react';
import TestRenderer from 'react-test-renderer';
import { FetchNote } from '../fetchNote';
// Mock dispatch
const dispatchMock = () => { return; };
describe('FetchNote', () => {
it('should renders correctly', () => {
const renderer = TestRenderer.create(
<FetchNote
notes={List<any>()}
loading={false}
error={''}
fetchAllNotes={dispatchMock}
fetchNote={dispatchMock}
addNote={dispatchMock}
editNote={dispatchMock}
removeNote={dispatchMock}
/>,
);
expect(renderer).toMatchSnapshot();
renderer.unmount();
});
});
================================================
FILE: frontend/src/pages/ReactPage/components/FetchNote/fetchNote.scss
================================================
@import 'sass/variables';
@import '../../reactPage';
.fetch-note-layout {
@extend .react-block;
}
.fetch-note-title {
@extend .react-block-title;
}
.fetch-note-collection {
margin: 25px 0 0 0;
}
.fetch-note-error-panel {
margin: 20px 0 0 0;
}
.fetch-note-filter-btn {
width: 30%;
}
@media only screen and (max-width: $small-screen) {
.fetch-note-filter-btn {
width: 80%;
max-width: 300px;
}
}
================================================
FILE: frontend/src/pages/ReactPage/components/FetchNote/fetchNote.tsx
================================================
import { List } from 'immutable';
import React from 'react';
import { Translation } from 'react-i18next';
import { connect } from 'react-redux';
import { AnyAction, Dispatch } from 'redux';
import { ADD_NOTE_REQUESTED, EDIT_NOTE_REQUESTED, FETCH_ALL_NOTES_REQUESTED, FETCH_NOTE_REQUESTED, REMOVE_NOTE_REQUESTED } from 'services/notes/constants';
import { INote } from 'services/notes/types';
import { IGlobalStateRecord } from 'types/global';
const styles = require('./fetchNote.scss');
// Component
interface IFetchNoteStateProps {
notes: List<INote>;
loading: boolean;
error: string;
}
interface IFetchNoteDispatchProps {
fetchAllNotes(): void;
fetchNote(id: number): void;
addNote(content: string): void;
editNote(id: number, content: string): void;
removeNote(id: number): void;
}
interface IFetchNoteProps extends IFetchNoteStateProps, IFetchNoteDispatchProps { }
export class FetchNote extends React.Component<IFetchNoteProps> {
public fetchAllNotes = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
this.props.fetchAllNotes();
}
public render() {
const noteList = this.props.notes.map((note) =>
(
<li className='collection-item'>
<div>Id: {note.id}   Content: {note.content}</div>
</li>
),
);
const noteListCollection = <ul className={`collection ${styles['fetch-note-collection']}`}> {noteList} </ul>;
const errorPanel = (
<div className={`card-panel red ${styles['fetch-note-error-panel']}`}>
<span className='white-text'>{this.props.error}</span>
</div>
);
return (
<Translation ns='reactPage'>
{(t) => (
<div className={`center-align z-depth-2 ${styles['fetch-note-layout']}`}>
<span className={styles['fetch-note-title']}>{t('fetchNote.asyncCalls')}</span>
<div className='fetchAllNotes'>
<a className={`btn waves-effect ${styles['fetch-note-filter-btn']}`} onClick={this.fetchAllNotes} role='button'>
{t('fetchNote.fetchAllNotes')}
</a>
</div>
{noteList.count() > 0 && noteListCollection}
{this.props.error !== '' && errorPanel}
</div>
)}
</Translation>
);
}
}
// Container
const mapStateToProps = (state: IGlobalStateRecord): IFetchNoteStateProps => ({
notes: state.get('notesState').notes,
loading: state.get('notesState').loading,
error: state.get('notesState').error,
});
const mapDispatchToProps = (dispatch: Dispatch<AnyAction>): IFetchNoteDispatchProps => ({
fetchAllNotes: () => {
dispatch({ type: FETCH_ALL_NOTES_REQUESTED, payload: {} });
},
fetchNote: (id: number) => {
dispatch({ type: FETCH_NOTE_REQUESTED, payload: { id } });
},
addNote: (content: string) => {
dispatch({ type: ADD_NOTE_REQUESTED, payload: { content } });
},
editNote: (id: number, content: string) => {
dispatch({ type: EDIT_NOTE_REQUESTED, payload: { id, content } });
},
removeNote: (id: number) => {
dispatch({ type: REMOVE_NOTE_REQUESTED, payload: { id } });
},
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(FetchNote);
================================================
FILE: frontend/src/pages/ReactPage/components/FetchNote/index.tsx
================================================
export { default } from './fetchNote';
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/__tests__/__snapshots__/todoLayout.spec.tsx.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TodoLayout should renders correctly 1`] = `
<div
className="center-align z-depth-2 todo-layout"
>
<span
className="todo-title"
>
title
</span>
<TodoInput />
<TodoList />
<TodoFooter />
</div>
`;
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/__tests__/todoLayout.spec.tsx
================================================
import React from 'react';
import TestRenderer from 'react-test-renderer';
import { TodoLayout } from '../todoLayout';
jest.mock('../components/TodoFooter', () => 'TodoFooter');
jest.mock('../components/TodoInput', () => 'TodoInput');
jest.mock('../components/TodoList', () => 'TodoList');
describe('TodoLayout', () => {
it('should renders correctly', () => {
const renderer = TestRenderer.create(
<TodoLayout />,
);
expect(renderer).toMatchSnapshot();
renderer.unmount();
});
});
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/__tests__/__snapshots__/todoFooter.spec.tsx.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TodoFooter should renders correctly 1`] = `
<div
className="todo-footer"
>
<TodoFilter
filter="SHOW_ALL"
>
todoLayout.todoFooter.all
</TodoFilter>
<TodoFilter
filter="SHOW_ACTIVE"
>
todoLayout.todoFooter.active
</TodoFilter>
<TodoFilter
filter="SHOW_COMPLETED"
>
todoLayout.todoFooter.completed
</TodoFilter>
</div>
`;
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/__tests__/todoFooter.spec.tsx
================================================
import React from 'react';
import TestRenderer from 'react-test-renderer';
import { TodoFooter } from '../todoFooter';
jest.mock('../components/TodoFilter', () => 'TodoFilter');
describe('TodoFooter', () => {
it('should renders correctly', () => {
const renderer = TestRenderer.create(
<TodoFooter />,
);
expect(renderer).toMatchSnapshot();
renderer.unmount();
});
});
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/components/TodoFilter/__tests__/__snapshots__/todoFilter.spec.tsx.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TodoFilter should renders correctly 1`] = `
<a
className="btn waves-effect waves-light todo-filter-btn"
href="#"
onClick={[Function]}
>
test
</a>
`;
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/components/TodoFilter/__tests__/todoFilter.spec.tsx
================================================
import React from 'react';
import TestRenderer from 'react-test-renderer';
import { TodoFilter } from '../todoFilter';
// Mock dispatch
const dispatchMock = () => { return; };
describe('TodoFilter', () => {
it('should renders correctly', () => {
const renderer = TestRenderer.create(
<TodoFilter active={true} setVisibilityFilter={dispatchMock}>
{'test'}
</TodoFilter>,
);
expect(renderer).toMatchSnapshot();
renderer.unmount();
});
});
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/components/TodoFilter/index.tsx
================================================
export { default } from './todoFilter';
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/components/TodoFilter/todoFilter.scss
================================================
@import 'sass/variables';
.todo-filter-btn {
width: 120px;
text-align: center;
margin: 3px;
padding: 0;
}
@media only screen and (max-width: $small-screen) {
.todo-filter-btn {
display: block;
width: 80%;
max-width: 300px;
margin: auto;
}
}
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/components/TodoFilter/todoFilter.tsx
================================================
import React from 'react';
import { connect } from 'react-redux';
import { AnyAction, Dispatch } from 'redux';
import { setVisibilityFilter } from 'services/todos/actions';
import { IGlobalStateRecord } from 'types/global';
const styles = require('./todoFilter.scss');
// Component
interface ITodoFilterStateProps {
active: boolean;
}
interface ITodoFilterDispatchProps {
setVisibilityFilter(): void;
}
interface ITodoFilterProps extends ITodoFilterStateProps, ITodoFilterDispatchProps { }
export class TodoFilter extends React.Component<ITodoFilterProps> {
public onClick = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
this.props.setVisibilityFilter();
}
public render() {
if (this.props.active) {
return (
<a href='#' className={`btn waves-effect waves-light ${styles['todo-filter-btn']}`} onClick={this.onClick}>
{this.props.children}
</a>
);
} else {
return (
<a href='#' className={`btn-flat waves-effect waves-light ${styles['todo-filter-btn']}`} onClick={this.onClick}>
{this.props.children}
</a>
);
}
}
}
// Container
interface ITodoFilterOwnProps {
filter: string;
}
const mapStateToProps = (state: IGlobalStateRecord, ownProps: ITodoFilterOwnProps): ITodoFilterStateProps => ({
active: ownProps.filter === state.get('todosState').visibilityFilter,
});
const mapDispatchToProps = (dispatch: Dispatch<AnyAction>, ownProps: ITodoFilterOwnProps): ITodoFilterDispatchProps => ({
setVisibilityFilter: () => {
dispatch(setVisibilityFilter(ownProps.filter));
},
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(TodoFilter);
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/index.tsx
================================================
export { default } from './todoFooter';
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/todoFooter.tsx
================================================
import React from 'react';
import { Translation } from 'react-i18next';
import TodoFilter from './components/TodoFilter';
export class TodoFooter extends React.Component {
public render() {
return (
<Translation ns='reactPage'>
{(t) => (
<div className='todo-footer'>
<TodoFilter filter='SHOW_ALL'>
{t('todoLayout.todoFooter.all')}
</TodoFilter>
<TodoFilter filter='SHOW_ACTIVE'>
{t('todoLayout.todoFooter.active')}
</TodoFilter>
<TodoFilter filter='SHOW_COMPLETED'>
{t('todoLayout.todoFooter.completed')}
</TodoFilter>
</div>
)}
</Translation>
);
}
}
export default TodoFooter;
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoInput/__tests__/__snapshots__/todoInput.spec.tsx.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TodoInput should renders correctly 1`] = `
<form
onSubmit={[Function]}
>
<div
className="input-field"
>
<input
id="input-addTodo"
type="text"
/>
<label>
todoLayout.todoInput.addTodo
</label>
</div>
</form>
`;
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoInput/__tests__/todoInput.spec.tsx
================================================
import React from 'react';
import TestRenderer from 'react-test-renderer';
import { TodoInput } from '../todoInput';
// Mock dispatch
const dispatchMock = () => { return; };
describe('TodoInput', () => {
it('should renders correctly', () => {
const renderer = TestRenderer.create(
<TodoInput onSubmit={dispatchMock} />,
);
expect(renderer).toMatchSnapshot();
renderer.unmount();
});
});
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoInput/index.tsx
================================================
export { default } from './todoInput';
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoInput/todoInput.tsx
================================================
import React, { Fragment } from 'react';
import { Translation } from 'react-i18next';
import { connect } from 'react-redux';
import { AnyAction, Dispatch } from 'redux';
import { addTodo } from 'services/todos/actions';
// Component
interface ITodoInputDispatchProps {
onSubmit(inputValue: string): void;
}
let input: HTMLInputElement;
export class TodoInput extends React.Component<ITodoInputDispatchProps> {
public onSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
e.preventDefault();
if (!input.value.trim()) {
return;
}
this.props.onSubmit(input.value);
input.value = '';
}
public setInput = (node: HTMLInputElement): void => {
input = node;
}
public render() {
return (
<Translation ns='reactPage'>
{(t) => (
<Fragment>
<form onSubmit={this.onSubmit}>
<div className='input-field'>
<input id='input-addTodo' type='text' ref={this.setInput} />
<label>{t('todoLayout.todoInput.addTodo')}</label>
</div>
</form>
</Fragment>
)}
</Translation>
);
}
}
// Container
const mapDispatchToProps = (dispatch: Dispatch<AnyAction>): ITodoInputDispatchProps => ({
onSubmit: (inputValue: string) => {
dispatch(addTodo(inputValue));
},
});
export default connect(
null,
mapDispatchToProps,
)(TodoInput);
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/__tests__/__snapshots__/todoList.spec.tsx.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TodoList should renders correctly 1`] = `null`;
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/__tests__/todoList.spec.tsx
================================================
import { List } from 'immutable';
import React from 'react';
import TestRenderer from 'react-test-renderer';
import { TodoList } from '../todoList';
jest.mock('../components/Todo', () => 'Todo');
// Mock dispatch
const dispatchMock = () => { return; };
describe('TodoList', () => {
it('should renders correctly', () => {
const renderer = TestRenderer.create(
<TodoList todos={List<any>()} toggleTodo={dispatchMock} />,
);
expect(renderer).toMatchSnapshot();
renderer.unmount();
});
});
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/components/Todo/__tests__/__snapshots__/todo.spec.tsx.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Todo should renders correctly 1`] = `
<a
className="collection-item waves-effect waves-teal"
href="#"
onClick={[Function]}
>
<div
className="truncate todo-incompleted"
>
text
</div>
</a>
`;
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/components/Todo/__tests__/todo.spec.tsx
================================================
import React from 'react';
import TestRenderer from 'react-test-renderer';
import Todo from '../todo';
// Mock dispatch
const dispatchMock = () => { return; };
describe('Todo', () => {
it('should renders correctly', () => {
const todo = {
id: 'id',
completed: false,
text: 'text',
};
const renderer = TestRenderer.create(
<Todo {...todo} onClick={dispatchMock} />,
);
expect(renderer).toMatchSnapshot();
renderer.unmount();
});
});
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/components/Todo/index.tsx
================================================
export { default } from './todo';
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/components/Todo/todo.scss
================================================
.todo-completed {
text-decoration: line-through;
color: grey;
}
.todo-incompleted {
text-decoration: none;
}
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/components/Todo/todo.tsx
================================================
import React from 'react';
import { ITodo } from 'services/todos/types';
const styles = require('./todo.scss');
interface ITodoProps extends ITodo {
onClick(e: React.MouseEvent<HTMLElement>): void;
}
export default class Todo extends React.Component<ITodoProps> {
public onClick = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
this.props.onClick(e);
}
public render() {
if (this.props.completed) {
return (
<a href='#' className='collection-item waves-effect' onClick={this.onClick} >
<div className={`truncate ${styles['todo-completed']}`}>
{this.props.text}
</div>
</a>
);
} else {
return (
<a href='#' className='collection-item waves-effect waves-teal' onClick={this.onClick} >
<div className={`truncate ${styles['todo-incompleted']}`}>
{this.props.text}
</div>
</a>
);
}
}
}
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/index.tsx
================================================
export { default } from './todoList';
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/todoList.tsx
================================================
import { List } from 'immutable';
import React from 'react';
import { connect } from 'react-redux';
import { AnyAction, Dispatch } from 'redux';
import { toggleTodo } from 'services/todos/actions';
import { VISIBILITY_FILTER_OPTIONS } from 'services/todos/constants';
import { ITodo, ITodosStateRecord } from 'services/todos/types';
import { IGlobalStateRecord } from 'types/global';
import Todo from './components/Todo';
// Component
interface ITodoListStateProps {
todos: List<ITodo>;
}
interface ITodoListDispatchProps {
toggleTodo(id: string): void;
}
interface ITodoListProps extends ITodoListStateProps, ITodoListDispatchProps { }
export class TodoList extends React.Component<ITodoListProps> {
public onClick = (id: string) => (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
this.props.toggleTodo(id);
}
public render() {
const todoList = this.props.todos.map((todo) =>
<Todo key={todo.id} {...todo} onClick={this.onClick(todo.id)} />,
);
if (todoList.count() > 0) {
return (
<ul className='collection'>
{todoList}
</ul>
);
} else {
return (null);
}
}
}
// Container
const getVisibleTodos = (todosState: ITodosStateRecord): List<ITodo> => {
switch (todosState.visibilityFilter) {
case VISIBILITY_FILTER_OPTIONS.SHOW_ALL:
return todosState.todos;
case VISIBILITY_FILTER_OPTIONS.SHOW_COMPLETED:
return todosState.todos.filter((t) => t !== undefined && t.completed);
case VISIBILITY_FILTER_OPTIONS.SHOW_ACTIVE:
return todosState.todos.filter((t) => t !== undefined && !t.completed);
default:
return todosState.todos;
}
};
const mapStateToProps = (state: IGlobalStateRecord): ITodoListStateProps => ({
todos: getVisibleTodos(state.get('todosState')),
});
const mapDispatchToProps = (dispatch: Dispatch<AnyAction>): ITodoListDispatchProps => ({
toggleTodo: (id: string) => {
dispatch(toggleTodo(id));
},
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(TodoList);
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/index.tsx
================================================
export { default } from './todoLayout';
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/todoLayout.scss
================================================
@import 'sass/variables';
@import '../../reactPage';
.todo-layout {
@extend .react-block;
}
.todo-title {
@extend .react-block-title;
}
================================================
FILE: frontend/src/pages/ReactPage/components/TodoLayout/todoLayout.tsx
================================================
import React from 'react';
import { Translation } from 'react-i18next';
import TodoFooter from './components/TodoFooter';
import TodoInput from './components/TodoInput';
import TodoList from './components/TodoList';
const styles = require('./todoLayout.scss');
export class TodoLayout extends React.Component {
public render() {
return (
<Translation ns='reactPage'>
{(t) => (
<div className={`center-align z-depth-2 ${styles['todo-layout']}`}>
<span className={styles['todo-title']}>{t('title')}</span>
<TodoInput />
<TodoList />
<TodoFooter />
</div>
)}
</Translation>
);
}
}
export default TodoLayout;
================================================
FILE: frontend/src/pages/ReactPage/index.tsx
================================================
export { default } from './reactPage';
================================================
FILE: frontend/src/pages/ReactPage/reactPage.scss
================================================
@import 'sass/variables';
.react-block {
border: 1px lightgray solid;
margin: 50px auto;
width: 850px;
padding: 20px 30px 40px;
}
@media only screen and (max-width: $medium-screen) {
.react-block {
max-width: 80%;
}
}
@media only screen and (max-width: $small-screen) {
.react-block {
max-width: 85%;
}
}
.react-block-title {
display: block;
font-weight: 100;
font-size: 4.5em;
color: $secondary-color;
margin-bottom: 20px;
}
@media only screen and (max-width: $medium-screen) {
.react-block-title {
font-weight: 100;
font-size: 4em;
}
}
@media only screen and (max-width: $small-screen) {
.react-block-title {
font-weight: 100;
font-size: 3em;
}
}
================================================
FILE: frontend/src/pages/ReactPage/reactPage.tsx
================================================
import React from 'react';
import { Translation } from 'react-i18next';
import FetchNote from './components/FetchNote';
import TodoLayout from './components/TodoLayout';
export class ReactPage extends React.Component {
public render() {
return (
<Translation ns='reactPage'>
{(t) => (
<div>
<div className='react-container'>
<h1 className='page-title'>{t('title')}</h1>
<TodoLayout />
<FetchNote />
</div>
</div>
)}
</Translation>
);
}
}
export default ReactPage;
================================================
FILE: frontend/src/reducers/index.tsx
================================================
import { connectRouter } from 'connected-react-router/immutable';
import { History } from 'history';
import { combineReducers } from 'redux-immutable';
import notesReducer from 'services/notes/reducer';
import todosReducer from 'services/todos/reducer';
import { IGlobalState } from 'types/global';
export default (history: History) => combineReducers<IGlobalState>({
router: connectRouter(history),
notesState: notesReducer,
todosState: todosReducer,
});
================================================
FILE: frontend/src/router.tsx
================================================
import React, { Fragment, lazy, Suspense } from 'react';
import { Route, Switch } from 'react-router';
import 'materialize-css';
import 'sass/global';
import { BodyContentLoader, FooterContentLoader, HeaderContentLoader } from 'components/ContentLoader';
import Footer from 'components/Footer';
import Header from 'components/Header';
const HomePage = lazy(() => import(
/*
webpackChunkName: "home-page",
webpackPreload: true
*/
'pages/HomePage'));
const NotFoundPage = lazy(() => import(
/*
webpackChunkName: "not-found-page",
webpackPrefetch: true
*/
'pages/NotFoundPage'));
const ParallaxPage = lazy(() => import(
/*
webpackChunkName: "parallax-page",
webpackPrefetch: true
*/
'pages/ParallaxPage'));
const ReactPage = lazy(() => import(
/*
webpackChunkName: "react-page",
webpackPrefetch: true
*/
'pages/ReactPage'));
export default (
<Fragment>
<Suspense fallback={<HeaderContentLoader />}>
<Header />
</Suspense>
<Suspense fallback={<BodyContentLoader />}>
<Switch>
<Route exact path='/' component={HomePage} />
<Route path='/react' component={ReactPage} />
<Route path='/parallax' component={ParallaxPage} />
<Route component={NotFoundPage} />
</Switch>
</Suspense>
<Suspense fallback={<FooterContentLoader />}>
<Footer />
</Suspense>
</Fragment>
);
================================================
FILE: frontend/src/sagas/index.tsx
================================================
import { all } from 'redux-saga/effects';
import notes from 'services/notes/sagas';
export default function* sagas() {
yield all([
notes(),
]);
}
================================================
FILE: frontend/src/sass/global.scss
================================================
@import "variables";
:global {
@import "~materialize-css/sass/materialize";
.container {
height: 100%;
}
.page-title {
color: $primary-color;
text-align: center;
}
@media only screen and (max-width: $medium-screen) {
.page-title {
font-size: 4em;
}
}
@media only screen and (max-width: $small-screen) {
.page-title {
font-size: 2.5em;
}
}
// Highlight css-module caused undefined class
.undefined {
border: solid white 3px;
outline: solid red 3px;
}
}
================================================
FILE: frontend/src/sass/variables.scss
================================================
// This file override materialize css variables
// https://github.com/Dogfalo/materialize/blob/master/sass/components/_variables.scss
$primary-color: #2196F3;
$secondary-color: #26A69A;
$carousel-height: 80%;
$small-screen: 600px;
$medium-screen: 992px;
$large-screen: 1200px;
================================================
FILE: frontend/src/services/notes/actions.tsx
================================================
import {ADD_NOTE_REQUESTED, EDIT_NOTE_REQUESTED, FETCH_ALL_NOTES_REQUESTED, FETCH_NOTE_REQUESTED, REMOVE_NOTE_REQUESTED } from './constants';
import { IActionAddNoteRequested, IActionEditNoteRequested, IActionFetchAllNotesRequested, IActionFetchNoteRequested, IActionRemoveNoteRequested } from './types';
export const fetchAllNotes = (payload: IActionFetchAllNotesRequested['payload']): IActionFetchAllNotesRequested => ({
type: FETCH_ALL_NOTES_REQUESTED,
payload,
});
export const fetchNote = (payload: IActionFetchNoteRequested['payload']): IActionFetchNoteRequested => ({
type: FETCH_NOTE_REQUESTED,
payload,
});
export const editNote = (payload: IActionEditNoteRequested['payload']): IActionEditNoteRequested => ({
type: EDIT_NOTE_REQUESTED,
payload,
});
export const addNote = (payload: IActionAddNoteRequested['payload']): IActionAddNoteRequested => ({
type: ADD_NOTE_REQUESTED,
payload,
});
export const removeNote = (payload: IActionRemoveNoteRequested['payload']): IActionRemoveNoteRequested => ({
type: REMOVE_NOTE_REQUESTED,
payload,
});
================================================
FILE: frontend/src/services/notes/apis.tsx
================================================
import axios from 'axios';
import { IActionAddNoteRequested, IActionEditNoteRequested, IActionFetchNoteRequested, IActionRemoveNoteRequested } from './types';
const notesUrl = 'api/v1/notes';
export default class NotesAPI {
public static fetchAll() {
return axios.get(`${notesUrl}`, {
headers: {
Accept: 'application/json',
},
}).then((res) => {
return res.data;
}).catch((err) => {
if (err.response != null) {
throw Error(err.response.data.error.message);
}
throw Error(err);
});
}
public static fetch(payload: IActionFetchNoteRequested['payload']) {
return axios.get(`${notesUrl}/${payload.id}`, {
headers: {
Accept: 'application/json',
},
}).then((res) => {
return res.data;
}).catch((err) => {
if (err.response != null) {
throw Error(err.response.data.error.message);
}
throw Error(err);
});
}
public static add(payload: IActionAddNoteRequested['payload']) {
return axios.post(notesUrl, payload, {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
}).then((res) => {
return res.data;
}).catch((err) => {
if (err.response != null) {
throw Error(err.response.data.error.message);
}
throw Error(err);
});
}
public static edit(payload: IActionEditNoteRequested['payload']) {
return axios.put(`${notesUrl}/${payload.id}`, payload, {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
}).then((res) => {
return res.data;
}).catch((err) => {
if (err.response != null) {
throw Error(err.response.data.error.message);
}
throw Error(err);
});
}
public static remove(payload: IActionRemoveNoteRequested['payload']) {
return axios.delete(`${notesUrl}/${payload.id}`, {
headers: {
Accept: 'application/json',
},
}).then((res) => {
return res.data;
}).catch((err) => {
if (err.response != null) {
throw Error(err.response.data.error.message);
}
throw Error(err);
});
}
}
================================================
FILE: frontend/src/services/notes/constants.tsx
================================================
import { IAsyncCall } from 'types/global';
export const FETCH_ALL_NOTES_REQUESTED = 'FETCH_ALL_NOTES/REQUESTED';
export const FETCH_ALL_NOTES_SUCCESS = 'FETCH_ALL_NOTES/SUCCESS';
export const FETCH_ALL_NOTES_FAILURE = 'FETCH_ALL_NOTES/FAILURE';
export const ASYNC_FETCH_ALL_NOTES: IAsyncCall = {
REQUESTED: FETCH_ALL_NOTES_REQUESTED,
SUCCESS: FETCH_ALL_NOTES_SUCCESS,
FAILURE: FETCH_ALL_NOTES_FAILURE,
};
export const FETCH_NOTE_REQUESTED = 'FETCH_NOTE/REQUESTED';
export const FETCH_NOTE_SUCCESS = 'FETCH_NOTE/SUCCESS';
export const FETCH_NOTE_FAILURE = 'FETCH_NOTE/FAILURE';
export const ASYNC_FETCH_NOTE: IAsyncCall = {
REQUESTED: FETCH_NOTE_REQUESTED,
SUCCESS: FETCH_NOTE_SUCCESS,
FAILURE: FETCH_NOTE_FAILURE,
};
export const EDIT_NOTE_REQUESTED = 'EDIT_NOTE/REQUESTED';
export const EDIT_NOTE_SUCCESS = 'EDIT_NOTE/SUCCESS';
export const EDIT_NOTE_FAILURE = 'EDIT_NOTE/FAILURE';
export const ASYNC_EDIT_NOTE: IAsyncCall = {
REQUESTED: EDIT_NOTE_REQUESTED,
SUCCESS: EDIT_NOTE_SUCCESS,
FAILURE: EDIT_NOTE_FAILURE,
};
export const ADD_NOTE_REQUESTED = 'ADD_NOTE/REQUESTED';
export const ADD_NOTE_SUCCESS = 'ADD_NOTE/SUCCESS';
export const ADD_NOTE_FAILURE = 'ADD_NOTE/FAILURE';
export const ASYNC_ADD_NOTE: IAsyncCall = {
REQUESTED: ADD_NOTE_REQUESTED,
SUCCESS: ADD_NOTE_SUCCESS,
FAILURE: ADD_NOTE_FAILURE,
};
export const REMOVE_NOTE_REQUESTED = 'REMOVE_NOTE/REQUESTED';
export const REMOVE_NOTE_SUCCESS = 'REMOVE_NOTE/SUCCESS';
export const REMOVE_NOTE_FAILURE = 'REMOVE_NOTE/FAILURE';
export const ASYNC_REMOVE_NOTE: IAsyncCall = {
REQUESTED: REMOVE_NOTE_REQUESTED,
SUCCESS: REMOVE_NOTE_SUCCESS,
FAILURE: REMOVE_NOTE_FAILURE,
};
================================================
FILE: frontend/src/services/notes/reducer.tsx
================================================
import { List, Record } from 'immutable';
import { FETCH_ALL_NOTES_FAILURE, FETCH_ALL_NOTES_REQUESTED, FETCH_ALL_NOTES_SUCCESS } from './constants';
import { IActionsNotes, INote, INotesState, INotesStateRecord } from './types';
export const getNotesStateRecord = (state: INotesState): INotesStateRecord => {
class NotesStateRecord extends Record(state) implements INotesStateRecord {}
return new NotesStateRecord();
};
const initialState = getNotesStateRecord({
notes: List<INote>(),
loading: false,
error: '',
});
export default (state: INotesStateRecord = initialState, action: IActionsNotes): INotesStateRecord => {
switch (action.type) {
case FETCH_ALL_NOTES_REQUESTED:
return state.set('loading', true);
case FETCH_ALL_NOTES_SUCCESS:
const noteList: INote[] = [];
action.payload.data.notes.forEach((note: INote) => {
noteList.push({
id: note.id,
content: note.content,
});
});
return state.clear().set('notes', List(noteList));
case FETCH_ALL_NOTES_FAILURE:
return state.clear().set('error', action.payload.error);
default:
return state;
}
};
================================================
FILE: frontend/src/services/notes/sagas.tsx
================================================
import { all, call, put, takeEvery } from 'redux-saga/effects';
import { IAsyncCall } from 'types/global';
import NotesAPI from './apis';
import { ASYNC_ADD_NOTE, ASYNC_EDIT_NOTE, ASYNC_FETCH_ALL_NOTES, ASYNC_FETCH_NOTE, ASYNC_REMOVE_NOTE } from './constants';
function* asyncHandler(action: IAsyncCall, api: (payload: any) => Promise<any>, payload: any) {
try {
const resJson = yield call(api, payload);
yield put({ type: action.SUCCESS, payload: { data: resJson.data } });
} catch (err) {
yield put({ type: action.FAILURE, payload: { error: err.message } });
}
}
function* sagaAsyncCallGenerator(action: IAsyncCall, api: (payload: any) => Promise<any>) {
yield takeEvery(action.REQUESTED, asyncHandler, action, api);
}
export default function* rootSaga() {
yield all([
sagaAsyncCallGenerator(ASYNC_FETCH_ALL_NOTES, NotesAPI.fetchAll),
sagaAsyncCallGenerator(ASYNC_FETCH_NOTE, NotesAPI.fetch),
sagaAsyncCallGenerator(ASYNC_ADD_NOTE, NotesAPI.add),
sagaAsyncCallGenerator(ASYNC_EDIT_NOTE, NotesAPI.edit),
sagaAsyncCallGenerator(ASYNC_REMOVE_NOTE, NotesAPI.remove),
]);
}
================================================
FILE: frontend/src/services/notes/types.d.ts
================================================
import { List, Record } from 'immutable';
import { ADD_NOTE_FAILURE, ADD_NOTE_REQUESTED, ADD_NOTE_SUCCESS, EDIT_NOTE_FAILURE, EDIT_NOTE_REQUESTED, EDIT_NOTE_SUCCESS, FETCH_ALL_NOTES_FAILURE, FETCH_ALL_NOTES_REQUESTED, FETCH_ALL_NOTES_SUCCESS, FETCH_NOTE_FAILURE, FETCH_NOTE_REQUESTED, FETCH_NOTE_SUCCESS, REMOVE_NOTE_FAILURE, REMOVE_NOTE_REQUESTED, REMOVE_NOTE_SUCCESS } from './constants';
// Notes state
export interface INotesState {
notes: List<INote>;
loading: boolean;
error: string;
}
export interface INote {
id: number;
content: string;
}
export interface INotesStateRecord extends Record<INotesState>, INotesState {}
// Notes actions
export interface IActionFetchAllNotesRequested {
type: typeof FETCH_ALL_NOTES_REQUESTED;
payload: {};
}
export interface IActionFetchAllNotesSuccess {
type: typeof FETCH_ALL_NOTES_SUCCESS;
payload: {
data: any,
};
}
export interface IActionFetchAllNotesFailure {
type: typeof FETCH_ALL_NOTES_FAILURE;
payload: {
error: string,
};
}
export interface IActionFetchNoteRequested {
type: typeof FETCH_NOTE_REQUESTED;
payload: {
id: number,
};
}
export interface IActionFetchNoteSuccess {
type: typeof FETCH_NOTE_SUCCESS;
payload: {
data: any,
};
}
export interface IActionFetchNoteFailure {
type: typeof FETCH_NOTE_FAILURE;
payload: {
error: string,
};
}
export interface IActionAddNoteRequested {
type: typeof ADD_NOTE_REQUESTED;
payload: {
content: string,
};
}
export interface IActionAddNoteSuccess {
type: typeof ADD_NOTE_SUCCESS;
payload: {
data: any,
};
}
export interface IActionAddNoteFailure {
type: typeof ADD_NOTE_FAILURE;
payload: {
error: string,
};
}
export interface IActionEditNoteRequested {
type: typeof EDIT_NOTE_REQUESTED;
payload: {
id: number,
content: string,
};
}
export interface IActionEditNoteSuccess {
type: typeof EDIT_NOTE_SUCCESS;
payload: {
data: any,
};
}
export interface IActionEditNoteFailure {
type: typeof EDIT_NOTE_FAILURE;
payload: {
error: string,
};
}
export interface IActionRemoveNoteRequested {
type: typeof REMOVE_NOTE_REQUESTED;
payload: {
id: number,
};
}
export interface IActionRemoveNoteSuccess {
type: typeof REMOVE_NOTE_SUCCESS;
payload: {
data: any,
};
}
export interface IActionRemoveNoteFailure {
type: typeof REMOVE_NOTE_FAILURE;
payload: {
error: string,
};
}
export type IActionsNotes
= IActionFetchAllNotesRequested
| IActionFetchAllNotesSuccess
| IActionFetchAllNotesFailure
| IActionFetchNoteRequested
| IActionFetchNoteSuccess
| IActionFetchNoteFailure
| IActionAddNoteRequested
| IActionAddNoteSuccess
| IActionAddNoteFailure
| IActionEditNoteRequested
| IActionEditNoteSuccess
| IActionEditNoteFailure
| IActionRemoveNoteRequested
| IActionRemoveNoteSuccess
| IActionRemoveNoteFailure;
================================================
FILE: frontend/src/services/todos/__test__/actions.spec.tsx
================================================
import { addTodo, setVisibilityFilter, toggleTodo } from '../actions';
import { ADD_TODO, SET_VISIBILITY_FILTER, TOGGLE_TODO, VISIBILITY_FILTER_OPTIONS } from '../constants';
import { IActionAddTodo, IActionSetVisibilityFilter, IActionToggleTodo } from '../types';
describe('[Actions] todos test', () => {
it('[addTodo] should return IActionAddTodo with input text, random id string and completed as false', () => {
const actionAddTodo: IActionAddTodo = addTodo('text');
expect(actionAddTodo.type === ADD_TODO).toBeTruthy();
expect(typeof actionAddTodo.id === 'string').toBeTruthy();
expect(actionAddTodo.text).toBe('text');
expect(actionAddTodo.completed).toBe(false);
});
it('[toggleTodo] should return IActionToggleTodo with input id', () => {
const actionToggleTodo: IActionToggleTodo = toggleTodo('id');
expect(actionToggleTodo.type === TOGGLE_TODO).toBeTruthy();
expect(actionToggleTodo.id).toBe('id');
});
it('[setVisibilityFilter] should return IActionSetVisibilityFilter with input filter', () => {
const actionSetVisibilityShowAll: IActionSetVisibilityFilter = setVisibilityFilter(VISIBILITY_FILTER_OPTIONS.SHOW_ALL);
expect(actionSetVisibilityShowAll.type === SET_VISIBILITY_FILTER).toBeTruthy();
expect(actionSetVisibilityShowAll.filter).toBe(VISIBILITY_FILTER_OPTIONS.SHOW_ALL);
const actionSetVisibilityShowActive: IActionSetVisibilityFilter = setVisibilityFilter(VISIBILITY_FILTER_OPTIONS.SHOW_ACTIVE);
expect(actionSetVisibilityShowActive.type === SET_VISIBILITY_FILTER).toBeTruthy();
expect(actionSetVisibilityShowActive.filter).toBe(VISIBILITY_FILTER_OPTIONS.SHOW_ACTIVE);
const actionSetVisibilityShowComleted: IActionSetVisibilityFilter = setVisibilityFilter(VISIBILITY_FILTER_OPTIONS.SHOW_COMPLETED);
expect(actionSetVisibilityShowComleted.type === SET_VISIBILITY_FILTER).toBeTruthy();
expect(actionSetVisibilityShowComleted.filter).toBe(VISIBILITY_FILTER_OPTIONS.SHOW_COMPLETED);
});
});
================================================
FILE: frontend/src/services/todos/__test__/reducer.spec.tsx
================================================
import { List } from 'immutable';
import { ADD_TODO, SET_VISIBILITY_FILTER, TOGGLE_TODO, VISIBILITY_FILTER_OPTIONS } from '../constants';
import reducer, { getTodosStateRecord } from '../reducer';
import { IActionAddTodo, IActionSetVisibilityFilter, IActionToggleTodo, ITodo } from '../types';
describe('[Reducers] todos test', () => {
const initialState = getTodosStateRecord({
todos: List<ITodo>([
{
id: 'initial_id',
text: 'initial_text',
completed: false,
},
]),
visibilityFilter: VISIBILITY_FILTER_OPTIONS.SHOW_ALL,
});
const actionAddTodo: IActionAddTodo = {
type: ADD_TODO,
id: 'test_id',
text: 'test_text',
completed: false,
};
const actionToggleTodo: IActionToggleTodo = {
type: TOGGLE_TODO,
id: 'initial_id',
};
const actionToggleTodoInvalid: IActionToggleTodo = {
type: TOGGLE_TODO,
id: 'invalid_id',
};
const actionShowAll: IActionSetVisibilityFilter = {
type: SET_VISIBILITY_FILTER,
filter: VISIBILITY_FILTER_OPTIONS.SHOW_ALL,
};
const actionShowActive: IActionSetVisibilityFilter = {
type: SET_VISIBILITY_FILTER,
filter: VISIBILITY_FILTER_OPTIONS.SHOW_ACTIVE,
};
const actionShowCompleted: IActionSetVisibilityFilter = {
type: SET_VISIBILITY_FILTER,
filter: VISIBILITY_FILTER_OPTIONS.SHOW_COMPLETED,
};
beforeEach(() => {
expect(initialState.todos.count()).toBe(1);
expect(initialState.visibilityFilter).toBe(VISIBILITY_FILTER_OPTIONS.SHOW_ALL);
});
it('[ADD_TODO] should return state with new todo added', () => {
const stateAddTodo = reducer(initialState, actionAddTodo);
expect(stateAddTodo.visibilityFilter).toEqual(initialState.visibilityFilter);
expect(stateAddTodo.todos).toEqual(List([
{
id: 'initial_id',
text: 'initial_text',
completed: false,
},
{
id: 'test_id',
text: 'test_text',
completed: false,
},
]));
});
it('[TOGGLE_TODO] should return state with one todo completed toggled', () => {
const stateToggleTodo = reducer(initialState, actionToggleTodo);
expect(stateToggleTodo.visibilityFilter).toEqual(initialState.visibilityFilter);
expect(stateToggleTodo.todos).toEqual(List([
{
id: 'initial_id',
text: 'initial_text',
completed: true,
},
]));
});
it('[TOGGLE_TODO] should return previous state if id is not found', () => {
const stateToggleTodoInvalid = reducer(initialState, actionToggleTodoInvalid);
expect(stateToggleTodoInvalid.visibilityFilter).toEqual(initialState.visibilityFilter);
expect(stateToggleTodoInvalid.todos).toEqual(initialState.todos);
});
it('[SET_VISIBILITY_FILTER] should return state with corresponding filter', () => {
const stateShowCompleted = reducer(initialState, actionShowCompleted);
expect(stateShowCompleted.visibilityFilter).toBe(VISIBILITY_FILTER_OPTIONS.SHOW_COMPLETED);
expect(stateShowCompleted.todos).toEqual(initialState.todos);
const stateShowActive = reducer(stateShowCompleted, actionShowActive);
expect(stateShowActive.visibilityFilter).toBe(VISIBILITY_FILTER_OPTIONS.SHOW_ACTIVE);
expect(stateShowActive.todos).toEqual(initialState.todos);
const stateShowAll = reducer(stateShowActive, actionShowAll);
expect(stateShowAll.visibilityFilter).toBe(VISIBILITY_FILTER_OPTIONS.SHOW_ALL);
expect(stateShowAll.todos).toEqual(initialState.todos);
});
it('[DEFAULT_ACTION] should return previous state if action is not found', () => {
const stateTestDefault = reducer(initialState, {} as any);
expect(stateTestDefault.visibilityFilter).toEqual(initialState.visibilityFilter);
expect(stateTestDefault.todos).toEqual(initialState.todos);
});
it('[UNDEFINED_STATE] should use default state if state is not defined', () => {
const stateTestUndefined = reducer(undefined, {} as any);
expect(stateTestUndefined.visibilityFilter).toEqual(initialState.visibilityFilter);
expect(stateTestUndefined.todos).toEqual(List([
{
id: 'fake_id',
text: 'Add your own todo task above, click to mark each todo as completed',
completed: false,
},
]));
});
});
================================================
FILE: frontend/src/services/todos/actions.tsx
================================================
import { v4 } from 'uuid';
import { ADD_TODO, SET_VISIBILITY_FILTER, TOGGLE_TODO } from './constants';
import { IActionAddTodo, IActionSetVisibilityFilter, IActionToggleTodo } from './types';
export const addTodo = (text: string): IActionAddTodo => ({
type: ADD_TODO,
id: v4(),
text,
completed: false,
});
export const toggleTodo = (id: string): IActionToggleTodo => ({
type: TOGGLE_TODO,
id,
});
export const setVisibilityFilter = (filter: string): IActionSetVisibilityFilter => ({
type: SET_VISIBILITY_FILTER,
filter,
});
================================================
FILE: frontend/src/services/todos/constants.tsx
================================================
// Todo action types
export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO';
// Visibility action types
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';
// Visibility filter options
export const VISIBILITY_FILTER_OPTIONS = {
SHOW_ALL: 'SHOW_ALL',
SHOW_COMPLETED: 'SHOW_COMPLETED',
SHOW_ACTIVE: 'SHOW_ACTIVE',
};
================================================
FILE: frontend/src/services/todos/reducer.tsx
================================================
import { List, Record } from 'immutable';
import { ADD_TODO, SET_VISIBILITY_FILTER, TOGGLE_TODO, VISIBILITY_FILTER_OPTIONS } from './constants';
import { IActionsTodo, ITodo, ITodosState, ITodosStateRecord } from './types';
export const getTodosStateRecord = (state: ITodosState): ITodosStateRecord => {
class TodosStateRecord extends Record(state) implements ITodosStateRecord {}
return new TodosStateRecord();
};
const initialState = getTodosStateRecord({
todos: List<ITodo>([
{
id: 'fake_id',
text: 'Add your own todo task above, click to mark each todo as completed',
completed: false,
},
]),
visibilityFilter: VISIBILITY_FILTER_OPTIONS.SHOW_ALL,
});
export default (state: ITodosStateRecord = initialState, action: IActionsTodo): ITodosStateRecord => {
switch (action.type) {
case ADD_TODO:
return state.update('todos', (todos) => todos.push({
id: action.id,
text: action.text,
completed: action.completed,
}));
case TOGGLE_TODO:
const index = state.todos.findIndex((s) => s !== undefined && s.id === action.id);
return state.update('todos', (todos) => index === -1 ? todos : todos.update(index, (s) => ({ ...s, completed: !s.completed })));
case SET_VISIBILITY_FILTER:
return state.set('visibilityFilter', action.filter);
default:
return state;
}
};
================================================
FILE: frontend/src/services/todos/types.d.ts
================================================
import { List, Record } from 'immutable';
import { ADD_TODO, SET_VISIBILITY_FILTER, TOGGLE_TODO } from './constants';
// Todos state
export interface ITodosState {
todos: List<ITodo>;
visibilityFilter: string;
}
export interface ITodo {
id: string;
text: string;
completed: boolean;
}
export interface ITodosStateRecord extends Record<ITodosState>, ITodosState {}
// Todos actions
export interface IActionAddTodo {
type: typeof ADD_TODO;
id: string;
text: string;
completed: boolean;
}
export interface IActionToggleTodo {
type: typeof TOGGLE_TODO;
id: string;
}
export interface IActionSetVisibilityFilter {
type: typeof SET_VISIBILITY_FILTER;
filter: string;
}
export type IActionsTodo
= IActionAddTodo
| IActionToggleTodo
| IActionSetVisibilityFilter;
================================================
FILE: frontend/src/store/index.tsx
================================================
import { routerMiddleware } from 'connected-react-router/immutable';
import { History } from 'history';
import Immutable from 'immutable';
import { applyMiddleware, compose, createStore, Store } from 'redux';
import logger from 'redux-logger';
import createSagaMiddleware from 'redux-saga';
import createRootReducer from 'reducers';
import sagas from 'sagas';
import { IGlobalState } from 'types/global';
export default (history: History, initialState: IGlobalState | {}): Store<IGlobalState> => {
// Create the saga middleware
const sagaMiddleware = createSagaMiddleware();
// Enhancer
const composeEnhancers =
typeof window === 'object' && (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
(window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
serialize: {
immutable: Immutable,
},
}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(routerMiddleware(history), sagaMiddleware, logger),
);
// Store
const store = createStore(createRootReducer(history), initialState, enhancer);
sagaMiddleware.run(sagas);
// Enable Webpack hot module replacement for reducers
if (module.hot) {
module.hot.accept('../reducers', () => {
store.replaceReducer(createRootReducer(history));
});
}
return store;
};
================================================
FILE: frontend/src/types/global.d.ts
================================================
import { RouterState } from 'connected-react-router/immutable';
import { Record } from 'immutable';
import { INotesState, INotesStateRecord } from 'services/notes/types';
import { ITodosState, ITodosStateRecord } from 'services/todos/types';
// Global state
export interface IGlobalState {
todosState: ITodosStateRecord;
notesState: INotesStateRecord;
router: RouterState;
}
export interface IGlobalStateRecord extends Record<IGlobalState>, IGlobalState {}
// Interface for async call steps
export interface IAsyncCall {
REQUESTED: string;
SUCCESS: string;
FAILURE: string;
}
================================================
FILE: frontend/src/utils/index.tsx
================================================
export const isProduction = process.env.NODE_ENV === 'production';
================================================
FILE: package.json
================================================
{
"name": "express-webpack-react-redux-typescript-boilerplate",
"version": "1.0.0",
"repository": {
"type": "git",
"url": "https://github.com/Armour/express-webpack-react-redux-typescript-boilerplate.git"
},
"author": "Chong Guo <armourcy@gmail.com>",
"license": "MIT",
"scripts": {
"clean": "trash frontend/dist coverage",
"dll": "cross-env NODE_ENV=production webpack --config webpack.config.dll.babel.js",
"dev": "yarn clean && yarn dll && concurrently \"yarn dev-server\" \"yarn dev-client\"",
"dev-server": "cross-env NODE_ENV=development nodemon --exec babel-node backend/server.js",
"dev-client": "cross-env NODE_ENV=development webpack-dev-server --hot --config webpack.config.dev.babel.js",
"prod": "yarn build && yarn serve",
"build": "yarn clean && yarn dll && cross-env NODE_ENV=production webpack --config webpack.config.prod.babel.js",
"serve": "cross-env NODE_ENV=production nodemon --exec babel-node backend/server.js",
"profile": "yarn clean && yarn dll && cross-env NODE_ENV=production webpack --config webpack.config.profile.babel.js",
"eslint": "eslint -f codeframe \"**/*.js\"",
"tslint": "tslint -t codeFrame -c tslint.json \"**/*.tsx\" \"**/*.d.ts\"",
"stylelint": "stylelint \"**/*.css **/*.sass **/*.scss\"",
"lint": "yarn eslint && yarn tslint && yarn stylelint",
"test": "jest --runInBand --coverage",
"coverage": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls"
},
"husky": {
"hooks": {
"pre-commit": "yarn lint",
"commit-msg": "commitlint -e $HUSKY_GIT_PARAMS",
"pre-push": "yarn test && yarn coverage"
}
},
"dependencies": {
"@babel/cli": "^7.2.3",
"@babel/core": "^7.2.2",
"@babel/plugin-proposal-class-properties": "^7.3.0",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/preset-env": "^7.3.1",
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.1.0",
"@types/i18next": "^12.1.0",
"@types/i18next-browser-languagedetector": "^2.0.1",
"@types/i18next-xhr-backend": "^1.4.1",
"@types/materialize-css": "^1.0.6",
"@types/prismjs": "^1.9.0",
"@types/react": "^16.8.2",
"@types/react-content-loader": "^3.1.4",
"@types/react-dom": "^16.8.0",
"@types/react-redux": "^7.0.1",
"@types/react-router": "^4.4.3",
"@types/react-router-dom": "^4.3.1",
"@types/redux-immutable": "^4.0.1",
"@types/redux-logger": "^3.0.6",
"@types/uuid": "^3.4.4",
"@types/webpack-env": "^1.13.7",
"add-asset-html-webpack-plugin": "^3.1.3",
"awesome-typescript-loader": "^5.2.1",
"axios": "^0.18.0",
"babel-loader": "^8.0.5",
"babel-plugin-prismjs": "^1.0.2",
"body-parser": "^1.18.3",
"colors": "^1.3.3",
"compression": "^1.7.3",
"concurrently": "^4.1.0",
"connect-redis": "^3.4.0",
"connected-react-router": "^6.2.2",
"cookie-parser": "^1.4.3",
"copy-webpack-plugin": "^4.6.0",
"cross-env": "^5.2.0",
"css-loader": "^2.1.0",
"cssnano": "^4.1.8",
"dotenv-webpack": "^1.7.0",
"duplicate-package-checker-webpack-plugin": "^3.0.0",
"express": "^4.16.4",
"express-session": "^1.15.6",
"file-loader": "^3.0.1",
"fork-ts-checker-webpack-plugin": "^0.5.2",
"helmet": "^3.15.0",
"history": "^4.7.2",
"html-webpack-plugin": "^3.2.0",
"http-status": "^1.3.1",
"i18next": "^14.1.1",
"i18next-browser-languagedetector": "^2.2.4",
"i18next-xhr-backend": "^1.5.1",
"image-webpack-loader": "^4.6.0",
"immutable": "4.0.0-rc.12",
"materialize-css": "1.0.0",
"mini-css-extract-plugin": "^0.5.0",
"morgan": "^1.9.1",
"node-sass": "^4.11.0",
"nodemon": "^1.18.9",
"offline-plugin": "^5.0.6",
"pg": "^7.8.0",
"postcss": "^7.0.14",
"postcss-import": "^12.0.1",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.5.0",
"prismjs": "^1.15.0",
"progress-bar-webpack-plugin": "^1.12.1",
"react": "^16.8.1",
"react-content-loader": "^4.0.1",
"react-dom": "^16.8.1",
"react-hot-loader": "^4.6.5",
"react-i18next": "^10.0.1",
"react-redux": "^6.0.0",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
"redux": "^4.0.1",
"redux-immutable": "^4.0.0",
"redux-logger": "^3.0.6",
"redux-saga": "^1.0.1",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"trash-cli": "^1.4.0",
"ts-loader": "^5.3.3",
"typescript": "^3.3.3",
"url-loader": "^1.1.2",
"uuid": "^3.3.2",
"webpack": "^4.29.3",
"webpack-bundle-analyzer": "^3.0.3",
"webpack-cli": "^3.2.3",
"webpack-merge": "^4.2.1",
"webpack-pwa-manifest": "^4.0.0"
},
"devDependencies": {
"@babel/node": "^7.2.2",
"@types/jest": "^24.0.0",
"@types/react-test-renderer": "^16.8.0",
"babel-core": "7.0.0-bridge.0",
"case-sensitive-paths-webpack-plugin": "^2.2.0",
"commitlint": "^7.5.0",
"commitlint-config-armour": "^1.2.1",
"coveralls": "^3.0.2",
"dotenv": "^6.2.0",
"eslint": "^5.13.0",
"eslint-config-airbnb": "^17.1.0",
"eslint-import-resolver-webpack": "^0.11.0",
"eslint-plugin-import": "^2.16.0",
"eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-react": "^7.12.4",
"husky": "1.3.1",
"identity-obj-proxy": "^3.0.0",
"jest": "^24.1.0",
"react-test-renderer": "^16.8.1",
"stylelint": "^9.10.1",
"stylelint-config-standard": "^18.2.0",
"ts-jest": "^23.10.5",
"tslint": "^5.12.1",
"tslint-react": "^3.6.0",
"webpack-dev-server": "^3.1.14"
},
"resolutions": {
"**/event-stream": "^4.0.1"
},
"browserslist": [
"> 1%",
"last 2 versions"
],
"nodemonConfig": {
"watch": [
"backend/controllers/",
"backend/routes/",
"backend/db/",
"backend/config.json",
"backend/server.js"
]
},
"jest": {
"preset": "ts-jest",
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.tsx",
"\\.(css|less|scss|sass)$": "identity-obj-proxy"
},
"moduleDirectories": [
"node_modules",
"frontend/src",
"backend"
],
"moduleFileExtensions": [
"js",
"jsx",
"json",
"ts",
"tsx"
]
}
}
================================================
FILE: tsconfig.json
================================================
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "frontend/src",
"target": "es6",
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"esModuleInterop": true,
"noEmitOnError": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"sourceMap": true,
"strict": true
},
"exclude": [
"node_modules",
"**/__tests__/**"
]
}
================================================
FILE: tslint.json
================================================
{
"extends": [
"tslint:recommended",
"tslint-react"
],
"rules": {
"jsx-boolean-value": false,
"jsx-no-multiline-js": false,
"max-line-length": false,
"no-console": [
true,
{
"allow": [
"warn",
"error"
]
}
],
"no-require-imports": false,
"no-submodule-imports": false,
"no-var-requires": false,
"newline-before-return": false,
"object-literal-sort-keys": false,
"quotemark": [
true,
"single",
"avoid-escape",
"avoid-template"
],
"trailing-comma": [
true,
{
"multiline": "always",
"singleline": "never"
}
],
"variable-name": [
true,
"ban-keywords"
]
},
"linterOptions": {
"exclude": [
"node_modules/**"
]
}
}
================================================
FILE: webpack.config.base.babel.js
================================================
import 'dotenv/config'; // Allow webpack config file to use .env variables
import path from 'path';
import webpack from 'webpack';
import cssnano from 'cssnano';
import postcssImport from 'postcss-import';
import postcssPresetEnv from 'postcss-preset-env';
import AddAssetHtmlPlugin from 'add-asset-html-webpack-plugin';
import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import DotenvPlugin from 'dotenv-webpack';
import DuplicatePackageCheckerPlugin from 'duplicate-package-checker-webpack-plugin';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import ProgressBarWebpackPlugin from 'progress-bar-webpack-plugin';
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
const ReactManifest = './frontend/dist/dll/react_manifest.json';
const MaterializeManifest = './frontend/dist/dll/materialize_manifest.json';
const I18nextManifest = './frontend/dist/dll/i18next_manifest.json';
const OtherManifest = './frontend/dist/dll/other_manifest.json';
const devMode = process.env.NODE_ENV !== 'production';
export default {
// The base directory, an absolute path, for resolving entry points and loaders from configuration
context: path.resolve(__dirname),
// Get mode from NODE_ENV
mode: process.env.NODE_ENV,
// Determine how the different types of modules within a project will be treated
module: {
rules: [
// Use babel-loader for ts(x) files
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
},
},
],
},
// Use a list of loaders to load materialize and prism css files
{
test: /\.css$/,
use: [
devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
sourceMap: !devMode,
modules: true,
importLoaders: 1,
},
},
{
loader: 'postcss-loader',
options: {
sourceMap: true,
plugins: () => [postcssImport, postcssPresetEnv, cssnano],
},
},
],
},
// Use a list of loaders to load scss files
{
test: /\.scss$/,
use: [
devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
sourceMap: !devMode,
modules: true,
importLoaders: 2,
},
},
{
loader: 'postcss-loader',
options: {
sourceMap: true,
plugins: () => [postcssImport, postcssPresetEnv, cssnano],
},
},
{ loader: 'sass-loader', options: { sourceMap: true } },
],
},
// Use image-webpack-loader and url-loader to load images
{
test: /\.(png|jpe?g|gif|svg|webp|tiff)(\?.*)?$/,
use: [
{ loader: 'url-loader', options: { limit: 10000, name: '[name].[ext]' } },
{ loader: 'image-webpack-loader', options: { disable: devMode } },
],
},
// Use url-loader to load font related files
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: [
{ loader: 'url-loader', options: { limit: 10000, name: '[name].[ext]' } },
],
},
// Use url-loader to load audio related files
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: [
{ loader: 'url-loader', options: { limit: 10000, name: '[name].[ext]' } },
],
},
],
},
// A list of used webpack plugins
plugins: [
// Enforces case sensitive paths.
new CaseSensitivePathsPlugin(),
// Supports dotenv file
new DotenvPlugin(),
// Warns when multiple versions of the same package exist in a build
new DuplicatePackageCheckerPlugin(),
// Load pre-build dll reference files
new webpack.DllReferencePlugin({ manifest: ReactManifest }),
new webpack.DllReferencePlugin({ manifest: MaterializeManifest }),
new webpack.DllReferencePlugin({ manifest: I18nextManifest }),
new webpack.DllReferencePlugin({ manifest: OtherManifest }),
// Extract css part from javascript bundle into separated file
new MiniCssExtractPlugin({
filename: '[name].[contenthash:10].css',
chunkFilename: '[name].[contenthash:10].css',
}),
// Better building progress display
new ProgressBarWebpackPlugin(),
// Runs typescript type checker on a separate process
new ForkTsCheckerWebpackPlugin(),
// Generate html file to dist folder
new HtmlWebpackPlugin({
title: 'Boilerplate',
template: path.resolve(__dirname, 'frontend/public/index.ejs'),
}),
// Add dll reference files to html
new AddAssetHtmlPlugin({
filepath: path.resolve(__dirname, 'frontend/dist/dll/*_dll.js'),
includeSourcemap: false,
}),
// Copy static files to build dir
new CopyWebpackPlugin([
{
from: 'frontend/public',
ignore: ['index.ejs'],
},
]),
],
// Change how modules are resolved
resolve: {
// What directories should be searched when resolving modules
modules: [
'node_modules',
'frontend/src',
],
// Automatically resolve certain extensions (Ex. import 'folder/name(.ext)')
extensions: [
'.ts',
'.tsx',
'.js',
'.jsx',
'.json',
'.css',
'.scss',
],
},
};
================================================
FILE: webpack.config.dev.babel.js
================================================
import path from 'path';
import merge from 'webpack-merge';
import BaseWebpackConfig from './webpack.config.base.babel';
export default merge(BaseWebpackConfig, {
// The point or points to enter the application.
entry: {
app: [
'./frontend/src/index',
],
},
// Affecting the output of the compilation
output: {
// path: the output directory as an absolute path (required)
path: path.resolve(__dirname, 'frontend/dist/dev'),
// filename: specifies the name of entry output file (required)
filename: '[name].[hash:10].js',
// chunkFilename: specifies the name of non-entry output files (e.g. dynamic import component)
chunkFilename: '[name].[hash:10].js',
},
devServer: {
// Port number for webpack dev server
port: process.env.PORT_WEBPACK_DEV_SERVER,
// Proxy for api call
proxy: {
'/api/v1': {
target: `http://localhost:${process.env.PORT}/`,
secure: false,
},
},
// Automatically open page
open: true,
// Serves index.html (contains 404 page in react-router) in place of any 404 responses
historyApiFallback: true,
// Shows a full-screen overlay when there are compiler errors
overlay: true,
},
// Source map mode
// https://webpack.js.org/configuration/devtool
devtool: 'eval-source-map',
});
================================================
FILE: webpack.config.dll.babel.js
================================================
import path from 'path';
import webpack from 'webpack';
import ProgressBarWebpackPlugin from 'progress-bar-webpack-plugin';
const reactVendors = [
'connected-react-router',
'connected-react-router/immutable',
'react',
'react-dom',
'react-hot-loader',
'react-i18next',
'redux-immutable',
'react-router-dom',
'react-redux',
'redux',
'redux-logger',
'redux-saga',
];
const materializeVendors = [
'materialize-css',
];
const i18nextVendors = [
'i18next',
'i18next-xhr-backend',
'i18next-browser-languagedetector',
];
const otherVendors = [
'axios',
'immutable',
'regenerator-runtime',
];
const config = {
// Get mode from NODE_ENV
mode: process.env.NODE_ENV,
// The base directory, an absolute path, for resolving entry points and loaders from configuration
context: path.resolve(__dirname),
// The point or points to enter the application.
entry: {
react: reactVendors,
materialize: materializeVendors,
i18next: i18nextVendors,
other: otherVendors,
},
// Affecting the output of the compilation
output: {
// path: the output directory as an absolute path (required)
path: path.resolve(__dirname, 'frontend/dist/dll/'),
// filename: specifies the name of output file on disk (required)
filename: '[name]_dll.js',
// library: name of the generated dll reference
library: '[name]_dll',
},
// A list of used webpack plugins
plugins: [
// Better building progress display
new ProgressBarWebpackPlugin(),
// Output manifest json file for each generated dll reference file
new webpack.DllPlugin({
path: path.resolve(__dirname, 'frontend/dist/dll/[name]_manifest.json'),
name: '[name]_dll',
format: true,
}),
],
// Turn off performance hints (assets size limit)
performance: {
hints: false,
},
};
export default config;
================================================
FILE: webpack.config.prod.babel.js
================================================
import path from 'path';
import merge from 'webpack-merge';
import OfflinePlugin from 'offline-plugin';
import BaseWebpackConfig from './webpack.config.base.babel';
export default merge(BaseWebpackConfig, {
// The point or points to enter the application.
entry: {
app: './frontend/src/index',
},
// Affecting the output of the compilation
output: {
// path: the output directory as an absolute path (required)
path: path.resolve(__dirname, 'frontend/dist/prod'),
// filename: specifies the name of entry output file (required)
filename: '[name].[chunkhash:10].js',
// chunkFilename: specifies the name of non-entry output files (e.g. dynamic import component)
chunkFilename: '[name].[chunkhash:10].js',
// publicPath: specifies the server-relative URL of the output resource directory
// https://webpack.js.org/configuration/output/#output-publicpath
publicPath: '/',
},
// A list of used webpack plugins
plugins: [
// It's always better if OfflinePlugin is the last plugin added
new OfflinePlugin(),
],
// Source map mode
// https://webpack.js.org/configuration/devtool
devtool: 'source-map',
});
================================================
FILE: webpack.config.profile.babel.js
================================================
import merge from 'webpack-merge';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import ProdWebpackConfig from './webpack.config.prod.babel';
export default merge(ProdWebpackConfig, {
plugins: [
// Webpack bundle analyzer for profiling
new BundleAnalyzerPlugin({
analyzerPort: process.env.PORT_BUNDLE_ANALYZER || 3005,
generateStatsFile: true,
}),
],
});
gitextract_w64eli_i/ ├── .appveyor.yml ├── .babelrc ├── .circleci/ │ └── config.yml ├── .commitlintrc.yml ├── .dockerignore ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .github/ │ ├── CODE_OF_CONDUCT.md │ ├── COMMIT_CONVENTION.md │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── main.workflow ├── .gitignore ├── .stylelintignore ├── .stylelintrc ├── Dockerfile ├── LICENSE ├── README.md ├── __mocks__/ │ ├── fileMock.tsx │ └── react-i18next.tsx ├── backend/ │ ├── config.json │ ├── controllers/ │ │ └── notes.js │ ├── db/ │ │ └── index.js │ ├── jsconfig.json │ ├── redis-data/ │ │ └── .gitkeep │ ├── routes/ │ │ ├── index.js │ │ └── notes.js │ ├── server.js │ └── sql/ │ ├── data.sql │ └── schema.sql ├── docker-compose.yml ├── frontend/ │ ├── public/ │ │ ├── browserconfig.xml │ │ ├── index.ejs │ │ ├── locales/ │ │ │ ├── en/ │ │ │ │ ├── common.json │ │ │ │ ├── homePage.json │ │ │ │ ├── notFoundPage.json │ │ │ │ ├── parallaxPage.json │ │ │ │ └── reactPage.json │ │ │ ├── jp/ │ │ │ │ ├── common.json │ │ │ │ ├── homePage.json │ │ │ │ ├── notFoundPage.json │ │ │ │ ├── parallaxPage.json │ │ │ │ └── reactPage.json │ │ │ └── zh/ │ │ │ ├── common.json │ │ │ ├── homePage.json │ │ │ ├── notFoundPage.json │ │ │ ├── parallaxPage.json │ │ │ └── reactPage.json │ │ ├── manifest.webmanifest │ │ └── robots.txt │ └── src/ │ ├── App.tsx │ ├── components/ │ │ ├── ContentLoader/ │ │ │ ├── __tests__/ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── contentLoader.spec.tsx.snap │ │ │ │ └── contentLoader.spec.tsx │ │ │ ├── contentLoader.scss │ │ │ ├── contentLoader.tsx │ │ │ └── index.tsx │ │ ├── Dropdown/ │ │ │ ├── __tests__/ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── dropdown.spec.tsx.snap │ │ │ │ └── dropdown.spec.tsx │ │ │ ├── dropdown.tsx │ │ │ └── index.tsx │ │ ├── Footer/ │ │ │ ├── __tests__/ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── footer.spec.tsx.snap │ │ │ │ └── footer.spec.tsx │ │ │ ├── footer.tsx │ │ │ └── index.tsx │ │ └── Header/ │ │ ├── __tests__/ │ │ │ ├── __snapshots__/ │ │ │ │ └── header.spec.tsx.snap │ │ │ └── header.spec.tsx │ │ ├── header.scss │ │ ├── header.tsx │ │ └── index.tsx │ ├── i18n/ │ │ └── index.tsx │ ├── index.tsx │ ├── pages/ │ │ ├── HomePage/ │ │ │ ├── __tests__/ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── homePage.spec.tsx.snap │ │ │ │ └── homePage.spec.tsx │ │ │ ├── components/ │ │ │ │ ├── Carousel/ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ │ └── carousel.spec.tsx.snap │ │ │ │ │ │ └── carousel.spec.tsx │ │ │ │ │ ├── carousel.tsx │ │ │ │ │ ├── constants/ │ │ │ │ │ │ └── carousel.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── Pushpin/ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ │ └── pushpin.spec.tsx.snap │ │ │ │ │ │ └── pushpin.spec.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── pushpin.scss │ │ │ │ │ └── pushpin.tsx │ │ │ │ └── TranslationButton/ │ │ │ │ ├── __tests__/ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ └── translationButton.spec.tsx.snap │ │ │ │ │ └── translationButton.spec.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── translationButton.tsx │ │ │ ├── homePage.scss │ │ │ ├── homePage.tsx │ │ │ └── index.tsx │ │ ├── NotFoundPage/ │ │ │ ├── __tests__/ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── notFoundPage.spec.tsx.snap │ │ │ │ └── notFoundPage.spec.tsx │ │ │ ├── index.tsx │ │ │ ├── notFoundPage.scss │ │ │ └── notFoundPage.tsx │ │ ├── ParallaxPage/ │ │ │ ├── __tests__/ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── parallaxPage.spec.tsx.snap │ │ │ │ └── parallaxPage.spec.tsx │ │ │ ├── components/ │ │ │ │ └── PrismCodes/ │ │ │ │ ├── __tests__/ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ └── prismCodes.spec.tsx.snap │ │ │ │ │ └── prismCodes.spec.tsx │ │ │ │ ├── constants/ │ │ │ │ │ └── prismCodes.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── prismCodes.scss │ │ │ │ └── prismCodes.tsx │ │ │ ├── index.tsx │ │ │ ├── parallaxPage.scss │ │ │ └── parallaxPage.tsx │ │ └── ReactPage/ │ │ ├── __tests__/ │ │ │ ├── __snapshots__/ │ │ │ │ └── reactPage.spec.tsx.snap │ │ │ └── reactPage.spec.tsx │ │ ├── components/ │ │ │ ├── FetchNote/ │ │ │ │ ├── __tests__/ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ └── fetchNote.spec.tsx.snap │ │ │ │ │ └── fetchNote.spec.tsx │ │ │ │ ├── fetchNote.scss │ │ │ │ ├── fetchNote.tsx │ │ │ │ └── index.tsx │ │ │ └── TodoLayout/ │ │ │ ├── __tests__/ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── todoLayout.spec.tsx.snap │ │ │ │ └── todoLayout.spec.tsx │ │ │ ├── components/ │ │ │ │ ├── TodoFooter/ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ │ └── todoFooter.spec.tsx.snap │ │ │ │ │ │ └── todoFooter.spec.tsx │ │ │ │ │ ├── components/ │ │ │ │ │ │ └── TodoFilter/ │ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ │ │ └── todoFilter.spec.tsx.snap │ │ │ │ │ │ │ └── todoFilter.spec.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── todoFilter.scss │ │ │ │ │ │ └── todoFilter.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── todoFooter.tsx │ │ │ │ ├── TodoInput/ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ │ └── todoInput.spec.tsx.snap │ │ │ │ │ │ └── todoInput.spec.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── todoInput.tsx │ │ │ │ └── TodoList/ │ │ │ │ ├── __tests__/ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ └── todoList.spec.tsx.snap │ │ │ │ │ └── todoList.spec.tsx │ │ │ │ ├── components/ │ │ │ │ │ └── Todo/ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ │ └── todo.spec.tsx.snap │ │ │ │ │ │ └── todo.spec.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── todo.scss │ │ │ │ │ └── todo.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── todoList.tsx │ │ │ ├── index.tsx │ │ │ ├── todoLayout.scss │ │ │ └── todoLayout.tsx │ │ ├── index.tsx │ │ ├── reactPage.scss │ │ └── reactPage.tsx │ ├── reducers/ │ │ └── index.tsx │ ├── router.tsx │ ├── sagas/ │ │ └── index.tsx │ ├── sass/ │ │ ├── global.scss │ │ └── variables.scss │ ├── services/ │ │ ├── notes/ │ │ │ ├── actions.tsx │ │ │ ├── apis.tsx │ │ │ ├── constants.tsx │ │ │ ├── reducer.tsx │ │ │ ├── sagas.tsx │ │ │ └── types.d.ts │ │ └── todos/ │ │ ├── __test__/ │ │ │ ├── actions.spec.tsx │ │ │ └── reducer.spec.tsx │ │ ├── actions.tsx │ │ ├── constants.tsx │ │ ├── reducer.tsx │ │ └── types.d.ts │ ├── store/ │ │ └── index.tsx │ ├── types/ │ │ └── global.d.ts │ └── utils/ │ └── index.tsx ├── package.json ├── tsconfig.json ├── tslint.json ├── webpack.config.base.babel.js ├── webpack.config.dev.babel.js ├── webpack.config.dll.babel.js ├── webpack.config.prod.babel.js └── webpack.config.profile.babel.js
SYMBOL INDEX (131 symbols across 30 files)
FILE: backend/sql/schema.sql
type notes (line 1) | Create Table notes (
FILE: frontend/src/App.tsx
type IAppProps (line 10) | interface IAppProps {
FILE: frontend/src/components/Dropdown/dropdown.tsx
type IDropdownProps (line 5) | interface IDropdownProps {
class Dropdown (line 14) | class Dropdown extends React.Component<IDropdownProps> {
method componentDidMount (line 15) | public componentDidMount() {
method render (line 20) | public render() {
FILE: frontend/src/components/Footer/footer.tsx
class Footer (line 4) | class Footer extends React.Component {
method render (line 5) | public render() {
FILE: frontend/src/components/Header/header.tsx
type IHeaderStateProps (line 13) | interface IHeaderStateProps {
class Header (line 17) | class Header extends React.Component<IHeaderStateProps> {
method constructor (line 18) | constructor(props: IHeaderStateProps) {
method checkActive (line 22) | public checkActive(urls: string[]) {
method componentDidMount (line 32) | public componentDidMount() {
method render (line 37) | public render() {
FILE: frontend/src/pages/HomePage/components/Carousel/carousel.tsx
class Carousel (line 22) | class Carousel extends React.Component {
method componentDidMount (line 25) | public componentDidMount() {
method componentWillUnmount (line 31) | public componentWillUnmount() {
method render (line 56) | public render() {
FILE: frontend/src/pages/HomePage/components/Carousel/constants/carousel.tsx
constant TOAST_DISPLAY_DURATION (line 2) | const TOAST_DISPLAY_DURATION = 3000;
constant TOOLTIP_DELAY_TIME (line 5) | const TOOLTIP_DELAY_TIME = 50;
constant CAROUSEL_AUTOPLAY_INTERVAL (line 8) | const CAROUSEL_AUTOPLAY_INTERVAL = 3500;
FILE: frontend/src/pages/HomePage/components/Pushpin/pushpin.tsx
type IPushpinProps (line 6) | interface IPushpinProps {
class Pushpin (line 11) | class Pushpin extends React.Component<IPushpinProps> {
method render (line 12) | public render() {
FILE: frontend/src/pages/HomePage/components/TranslationButton/translationButton.tsx
class TranslationButton (line 9) | class TranslationButton extends React.Component {
method componentDidMount (line 10) | public componentDidMount() {
method render (line 20) | public render() {
FILE: frontend/src/pages/HomePage/homePage.tsx
class HomePage (line 10) | class HomePage extends React.Component {
method componentDidMount (line 11) | public componentDidMount() {
method render (line 28) | public render() {
FILE: frontend/src/pages/NotFoundPage/notFoundPage.tsx
type INotFoundPageState (line 13) | interface INotFoundPageState {
class NotFoundPage (line 17) | class NotFoundPage extends React.Component<{}, INotFoundPageState> {
method constructor (line 18) | constructor(props: {}) {
method render (line 29) | public render() {
FILE: frontend/src/pages/ParallaxPage/components/PrismCodes/constants/prismCodes.tsx
constant PARALLAX_CODE (line 2) | const PARALLAX_CODE = `
FILE: frontend/src/pages/ParallaxPage/components/PrismCodes/prismCodes.tsx
type IPrismCodesProps (line 7) | interface IPrismCodesProps {
class PrismCodes (line 11) | class PrismCodes extends React.Component<IPrismCodesProps> {
method componentDidMount (line 12) | public componentDidMount() {
method render (line 16) | public render() {
FILE: frontend/src/pages/ParallaxPage/parallaxPage.tsx
class ParallaxPage (line 8) | class ParallaxPage extends React.Component {
method componentDidMount (line 9) | public componentDidMount() {
method render (line 14) | public render() {
FILE: frontend/src/pages/ReactPage/components/FetchNote/fetchNote.tsx
type IFetchNoteStateProps (line 15) | interface IFetchNoteStateProps {
type IFetchNoteDispatchProps (line 21) | interface IFetchNoteDispatchProps {
type IFetchNoteProps (line 29) | interface IFetchNoteProps extends IFetchNoteStateProps, IFetchNoteDispat...
class FetchNote (line 31) | class FetchNote extends React.Component<IFetchNoteProps> {
method render (line 37) | public render() {
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/components/TodoFilter/todoFilter.tsx
type ITodoFilterStateProps (line 12) | interface ITodoFilterStateProps {
type ITodoFilterDispatchProps (line 16) | interface ITodoFilterDispatchProps {
type ITodoFilterProps (line 20) | interface ITodoFilterProps extends ITodoFilterStateProps, ITodoFilterDis...
class TodoFilter (line 22) | class TodoFilter extends React.Component<ITodoFilterProps> {
method render (line 28) | public render() {
type ITodoFilterOwnProps (line 47) | interface ITodoFilterOwnProps {
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/todoFooter.tsx
class TodoFooter (line 6) | class TodoFooter extends React.Component {
method render (line 7) | public render() {
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoInput/todoInput.tsx
type ITodoInputDispatchProps (line 10) | interface ITodoInputDispatchProps {
class TodoInput (line 16) | class TodoInput extends React.Component<ITodoInputDispatchProps> {
method render (line 30) | public render() {
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/components/Todo/todo.tsx
type ITodoProps (line 7) | interface ITodoProps extends ITodo {
class Todo (line 11) | class Todo extends React.Component<ITodoProps> {
method render (line 17) | public render() {
FILE: frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/todoList.tsx
type ITodoListStateProps (line 14) | interface ITodoListStateProps {
type ITodoListDispatchProps (line 18) | interface ITodoListDispatchProps {
type ITodoListProps (line 22) | interface ITodoListProps extends ITodoListStateProps, ITodoListDispatchP...
class TodoList (line 24) | class TodoList extends React.Component<ITodoListProps> {
method render (line 30) | public render() {
FILE: frontend/src/pages/ReactPage/components/TodoLayout/todoLayout.tsx
class TodoLayout (line 10) | class TodoLayout extends React.Component {
method render (line 11) | public render() {
FILE: frontend/src/pages/ReactPage/reactPage.tsx
class ReactPage (line 7) | class ReactPage extends React.Component {
method render (line 8) | public render() {
FILE: frontend/src/services/notes/apis.tsx
class NotesAPI (line 7) | class NotesAPI {
method fetchAll (line 8) | public static fetchAll() {
method fetch (line 23) | public static fetch(payload: IActionFetchNoteRequested['payload']) {
method add (line 38) | public static add(payload: IActionAddNoteRequested['payload']) {
method edit (line 54) | public static edit(payload: IActionEditNoteRequested['payload']) {
method remove (line 70) | public static remove(payload: IActionRemoveNoteRequested['payload']) {
FILE: frontend/src/services/notes/constants.tsx
constant FETCH_ALL_NOTES_REQUESTED (line 3) | const FETCH_ALL_NOTES_REQUESTED = 'FETCH_ALL_NOTES/REQUESTED';
constant FETCH_ALL_NOTES_SUCCESS (line 4) | const FETCH_ALL_NOTES_SUCCESS = 'FETCH_ALL_NOTES/SUCCESS';
constant FETCH_ALL_NOTES_FAILURE (line 5) | const FETCH_ALL_NOTES_FAILURE = 'FETCH_ALL_NOTES/FAILURE';
constant ASYNC_FETCH_ALL_NOTES (line 6) | const ASYNC_FETCH_ALL_NOTES: IAsyncCall = {
constant FETCH_NOTE_REQUESTED (line 11) | const FETCH_NOTE_REQUESTED = 'FETCH_NOTE/REQUESTED';
constant FETCH_NOTE_SUCCESS (line 12) | const FETCH_NOTE_SUCCESS = 'FETCH_NOTE/SUCCESS';
constant FETCH_NOTE_FAILURE (line 13) | const FETCH_NOTE_FAILURE = 'FETCH_NOTE/FAILURE';
constant ASYNC_FETCH_NOTE (line 14) | const ASYNC_FETCH_NOTE: IAsyncCall = {
constant EDIT_NOTE_REQUESTED (line 19) | const EDIT_NOTE_REQUESTED = 'EDIT_NOTE/REQUESTED';
constant EDIT_NOTE_SUCCESS (line 20) | const EDIT_NOTE_SUCCESS = 'EDIT_NOTE/SUCCESS';
constant EDIT_NOTE_FAILURE (line 21) | const EDIT_NOTE_FAILURE = 'EDIT_NOTE/FAILURE';
constant ASYNC_EDIT_NOTE (line 22) | const ASYNC_EDIT_NOTE: IAsyncCall = {
constant ADD_NOTE_REQUESTED (line 27) | const ADD_NOTE_REQUESTED = 'ADD_NOTE/REQUESTED';
constant ADD_NOTE_SUCCESS (line 28) | const ADD_NOTE_SUCCESS = 'ADD_NOTE/SUCCESS';
constant ADD_NOTE_FAILURE (line 29) | const ADD_NOTE_FAILURE = 'ADD_NOTE/FAILURE';
constant ASYNC_ADD_NOTE (line 30) | const ASYNC_ADD_NOTE: IAsyncCall = {
constant REMOVE_NOTE_REQUESTED (line 35) | const REMOVE_NOTE_REQUESTED = 'REMOVE_NOTE/REQUESTED';
constant REMOVE_NOTE_SUCCESS (line 36) | const REMOVE_NOTE_SUCCESS = 'REMOVE_NOTE/SUCCESS';
constant REMOVE_NOTE_FAILURE (line 37) | const REMOVE_NOTE_FAILURE = 'REMOVE_NOTE/FAILURE';
constant ASYNC_REMOVE_NOTE (line 38) | const ASYNC_REMOVE_NOTE: IAsyncCall = {
FILE: frontend/src/services/notes/reducer.tsx
class NotesStateRecord (line 7) | class NotesStateRecord extends Record(state) implements INotesStateRecor...
FILE: frontend/src/services/notes/types.d.ts
type INotesState (line 6) | interface INotesState {
type INote (line 11) | interface INote {
type INotesStateRecord (line 15) | interface INotesStateRecord extends Record<INotesState>, INotesState {}
type IActionFetchAllNotesRequested (line 18) | interface IActionFetchAllNotesRequested {
type IActionFetchAllNotesSuccess (line 22) | interface IActionFetchAllNotesSuccess {
type IActionFetchAllNotesFailure (line 28) | interface IActionFetchAllNotesFailure {
type IActionFetchNoteRequested (line 35) | interface IActionFetchNoteRequested {
type IActionFetchNoteSuccess (line 41) | interface IActionFetchNoteSuccess {
type IActionFetchNoteFailure (line 47) | interface IActionFetchNoteFailure {
type IActionAddNoteRequested (line 54) | interface IActionAddNoteRequested {
type IActionAddNoteSuccess (line 60) | interface IActionAddNoteSuccess {
type IActionAddNoteFailure (line 66) | interface IActionAddNoteFailure {
type IActionEditNoteRequested (line 73) | interface IActionEditNoteRequested {
type IActionEditNoteSuccess (line 80) | interface IActionEditNoteSuccess {
type IActionEditNoteFailure (line 86) | interface IActionEditNoteFailure {
type IActionRemoveNoteRequested (line 93) | interface IActionRemoveNoteRequested {
type IActionRemoveNoteSuccess (line 99) | interface IActionRemoveNoteSuccess {
type IActionRemoveNoteFailure (line 105) | interface IActionRemoveNoteFailure {
type IActionsNotes (line 112) | type IActionsNotes
FILE: frontend/src/services/todos/constants.tsx
constant ADD_TODO (line 2) | const ADD_TODO = 'ADD_TODO';
constant TOGGLE_TODO (line 3) | const TOGGLE_TODO = 'TOGGLE_TODO';
constant SET_VISIBILITY_FILTER (line 6) | const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';
constant VISIBILITY_FILTER_OPTIONS (line 9) | const VISIBILITY_FILTER_OPTIONS = {
FILE: frontend/src/services/todos/reducer.tsx
class TodosStateRecord (line 7) | class TodosStateRecord extends Record(state) implements ITodosStateRecor...
FILE: frontend/src/services/todos/types.d.ts
type ITodosState (line 6) | interface ITodosState {
type ITodo (line 10) | interface ITodo {
type ITodosStateRecord (line 15) | interface ITodosStateRecord extends Record<ITodosState>, ITodosState {}
type IActionAddTodo (line 18) | interface IActionAddTodo {
type IActionToggleTodo (line 24) | interface IActionToggleTodo {
type IActionSetVisibilityFilter (line 28) | interface IActionSetVisibilityFilter {
type IActionsTodo (line 33) | type IActionsTodo
FILE: frontend/src/types/global.d.ts
type IGlobalState (line 8) | interface IGlobalState {
type IGlobalStateRecord (line 13) | interface IGlobalStateRecord extends Record<IGlobalState>, IGlobalState {}
type IAsyncCall (line 16) | interface IAsyncCall {
Condensed preview — 174 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (173K chars).
[
{
"path": ".appveyor.yml",
"chars": 205,
"preview": "environment:\n nodejs_version: \"11\"\n\ninstall:\n - ps: Install-Product node $env:nodejs_version\n - yarn install\n\nbuild_s"
},
{
"path": ".babelrc",
"chars": 814,
"preview": "{\n \"presets\": [\n \"@babel/preset-env\",\n \"@babel/preset-react\",\n \"@babel/preset-typescript\"\n ],\n \"plugins\": [\n"
},
{
"path": ".circleci/config.yml",
"chars": 1227,
"preview": "# Javascript Node CircleCI 2.0 configuration file\n#\n# Check https://circleci.com/docs/2.0/language-javascript/ for more "
},
{
"path": ".commitlintrc.yml",
"chars": 20,
"preview": "extends: ['armour']\n"
},
{
"path": ".dockerignore",
"chars": 471,
"preview": "# General\n.DS_Store\n*~\n*.swp\n*.log\n\n# Thumbnails\n._*\nThumbs.db\n\n# Trash folder or files\n.Trashes\n.Trash-*\n\n# Folder conf"
},
{
"path": ".editorconfig",
"chars": 807,
"preview": "# http://editorconfig.org\n\n# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines with a newline ending every f"
},
{
"path": ".eslintignore",
"chars": 30,
"preview": "node_modules/\ndist/\ncoverage/\n"
},
{
"path": ".eslintrc",
"chars": 702,
"preview": "{\n \"env\": {\n \"browser\": true,\n \"node\": true,\n \"es6\": true\n },\n \"extends\": [\n \"airbnb\"\n ],\n \"rules\": {\n "
},
{
"path": ".gitattributes",
"chars": 1769,
"preview": "# Details per file setting:\n# text These files should be normalized (i.e. convert CRLF to LF).\n# binary These fi"
},
{
"path": ".github/CODE_OF_CONDUCT.md",
"chars": 3303,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": ".github/COMMIT_CONVENTION.md",
"chars": 4644,
"preview": "# Git Commit Message Convention\n\n> This is adapted from [Angular's commit convention](https://github.com/conventional-ch"
},
{
"path": ".github/CONTRIBUTING.md",
"chars": 4590,
"preview": "# Contributing\n\n## Code of Conduct\n\nHelp us keep express-webpack-react-redux-typescript-boilerplate open and inclusive. "
},
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 1092,
"preview": "<!--\nPLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.\n\nISSUES MISSING IMPORTANT INFOR"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 979,
"preview": "<!--\nPlease make sure to read the Pull Request Guidelines:\nhttps://github.com/Armour/express-webpack-react-redux-typescr"
},
{
"path": ".github/main.workflow",
"chars": 870,
"preview": "workflow \"Deploy on Heroku\" {\n on = \"push\"\n resolves = [\n \"verify\",\n ]\n}\n\n# Login\naction \"login\" {\n uses = \"actio"
},
{
"path": ".gitignore",
"chars": 456,
"preview": "# General\n.DS_Store\n*~\n*.swp\n*.log\n\n# Thumbnails\n._*\nThumbs.db\n\n# Trash folder or files\n.Trashes\n.Trash-*\n\n# Folder conf"
},
{
"path": ".stylelintignore",
"chars": 30,
"preview": "node_modules/\ndist/\ncoverage/\n"
},
{
"path": ".stylelintrc",
"chars": 45,
"preview": "{\n \"extends\": \"stylelint-config-standard\"\n}\n"
},
{
"path": "Dockerfile",
"chars": 242,
"preview": "FROM node:alpine\n\nARG PORT=${PORT}\n\nWORKDIR /usr/src/app\nCOPY . /usr/src/app/\n\nRUN apk add --no-cache --update make gcc "
},
{
"path": "LICENSE",
"chars": 1066,
"preview": "MIT License\n\nCopyright (c) 2017 Chong Guo\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\n"
},
{
"path": "README.md",
"chars": 7267,
"preview": "# express-webpack-react-redux-typescript-boilerplate\n\n[ => children((k: string) => k, { i18n: {} });\n"
},
{
"path": "backend/config.json",
"chars": 285,
"preview": "{\n \"pgsql_hostname_dev\": \"localhost\",\n \"pgsql_hostname_prod\": \"postgres\",\n \"pgsql_username\": \"docker\",\n \"pgsql_passw"
},
{
"path": "backend/controllers/notes.js",
"chars": 3400,
"preview": "import status from 'http-status';\n\nimport db from '../db';\n\nexport const getAllNotes = async (req, res) => {\n try {\n "
},
{
"path": "backend/db/index.js",
"chars": 1076,
"preview": "import { Pool } from 'pg';\n\nimport config from '../config.json';\n\nconst isProduction = process.env.NODE_ENV === 'product"
},
{
"path": "backend/jsconfig.json",
"chars": 50,
"preview": "{\n \"compilerOptions\": {\n \"baseUrl\": \".\"\n }\n}\n"
},
{
"path": "backend/redis-data/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "backend/routes/index.js",
"chars": 155,
"preview": "import express from 'express';\n\nimport notesRtr from './notes';\n\nconst router = express.Router();\n\nrouter.use('/notes', "
},
{
"path": "backend/routes/notes.js",
"chars": 337,
"preview": "import express from 'express';\n\nimport {\n getAllNotes, getNote, addNote, editNote, removeNote,\n} from '../controllers/n"
},
{
"path": "backend/server.js",
"chars": 2393,
"preview": "import express from 'express';\nimport path from 'path';\nimport logger from 'morgan';\nimport cookieParser from 'cookie-pa"
},
{
"path": "backend/sql/data.sql",
"chars": 104,
"preview": "INSERT INTO notes (content) VALUES ('note data 1');\nINSERT INTO notes (content) VALUES ('note data 2');\n"
},
{
"path": "backend/sql/schema.sql",
"chars": 116,
"preview": "Create Table notes (\n id serial primary key,\n content text not null,\n created timestamptz default now()\n);\n"
},
{
"path": "docker-compose.yml",
"chars": 1428,
"preview": "version: '3.7'\n\nservices:\n web:\n image: cguo/express-webpack-react-redux-typescript-boilerplate\n build: # ignored"
},
{
"path": "frontend/public/browserconfig.xml",
"chars": 246,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n <msapplication>\n <tile>\n <square150x150logo"
},
{
"path": "frontend/public/index.ejs",
"chars": 2395,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">"
},
{
"path": "frontend/public/locales/en/common.json",
"chars": 432,
"preview": "{\n \"header\": {\n \"react\": \"React\",\n \"dropdown\": \"Dropdown\",\n \"notfound\": \"404\"\n },\n \"footer\": {\n \"title\": "
},
{
"path": "frontend/public/locales/en/homePage.json",
"chars": 811,
"preview": "{\n \"title\": \"Home Page\",\n \"carousel\": {\n \"focusButtonText\": \"Focus me!\",\n \"firstPanelTitle\": \"First Panel\",\n "
},
{
"path": "frontend/public/locales/en/notFoundPage.json",
"chars": 36,
"preview": "{\n \"title\": \"404 Page Not Found\"\n}\n"
},
{
"path": "frontend/public/locales/en/parallaxPage.json",
"chars": 200,
"preview": "{\n \"title\": \"Parallax Page\",\n \"description\": \"Parallax is an effect where the background content or image in this case"
},
{
"path": "frontend/public/locales/en/reactPage.json",
"chars": 332,
"preview": "{\n \"title\": \"React Page\",\n \"fetchNote\": {\n \"asyncCalls\": \"async calls\",\n \"fetchAllNotes\": \"Fetch All Notes From "
},
{
"path": "frontend/public/locales/jp/common.json",
"chars": 378,
"preview": "{\n \"header\": {\n \"react\": \"React\",\n \"dropdown\": \"ドロップダウンリスト\",\n \"notfound\": \"404\"\n },\n \"footer\": {\n \"title\""
},
{
"path": "frontend/public/locales/jp/homePage.json",
"chars": 691,
"preview": "{\n \"title\": \"ホームページ\",\n \"carousel\": {\n \"focusButtonText\": \"フォーカス!\",\n \"firstPanelTitle\": \"最初のパネル\",\n \"secondPane"
},
{
"path": "frontend/public/locales/jp/notFoundPage.json",
"chars": 33,
"preview": "{\n \"title\": \"404 ページが見つかりません\"\n}\n"
},
{
"path": "frontend/public/locales/jp/parallaxPage.json",
"chars": 127,
"preview": "{\n \"title\": \"視差ページ\",\n \"description\": \"Parallax(パララックス)は、この場合のバックグラウンドコンテンツまたは画像がスクロール中にフォアグラウンドコンテンツとは異なる速度で移動される場合の効果"
},
{
"path": "frontend/public/locales/jp/reactPage.json",
"chars": 303,
"preview": "{\n \"title\": \"Reactページ\",\n \"fetchNote\": {\n \"asyncCalls\": \"非同期関数\",\n \"fetchAllNotes\": \"DB内のメモを取得する\"\n },\n \"todoLayo"
},
{
"path": "frontend/public/locales/zh/common.json",
"chars": 350,
"preview": "{\n \"header\": {\n \"react\": \"React\",\n \"dropdown\": \"下拉列表\",\n \"notfound\": \"404\"\n },\n \"footer\": {\n \"title\": \"页脚内"
},
{
"path": "frontend/public/locales/zh/homePage.json",
"chars": 648,
"preview": "{\n \"title\": \"首页\",\n \"carousel\": {\n \"focusButtonText\": \"看我看我!\",\n \"firstPanelTitle\": \"第一个面板\",\n \"secondPanelTitle"
},
{
"path": "frontend/public/locales/zh/notFoundPage.json",
"chars": 27,
"preview": "{\n \"title\": \"404 页面不存在\"\n}\n"
},
{
"path": "frontend/public/locales/zh/parallaxPage.json",
"chars": 88,
"preview": "{\n \"title\": \"视差页面\",\n \"description\": \"Parallax(视差) 是指一种背景内容或图像在滚动时以与前景内容不同的速度移动的效果。\"\n}\n"
},
{
"path": "frontend/public/locales/zh/reactPage.json",
"chars": 294,
"preview": "{\n \"title\": \"React页面\",\n \"fetchNote\": {\n \"asyncCalls\": \"异步函数\",\n \"fetchAllNotes\": \"获取数据库所有笔记\"\n },\n \"todoLayout\":"
},
{
"path": "frontend/public/manifest.webmanifest",
"chars": 539,
"preview": "{\n \"name\": \"Boilerplate\",\n \"short_name\": \"Boilerplate\",\n \"start_url\": \".\",\n \"display\": \"standalone\",\n \"orientation\""
},
{
"path": "frontend/public/robots.txt",
"chars": 24,
"preview": "User-agent: *\nDisallow:\n"
},
{
"path": "frontend/src/App.tsx",
"chars": 525,
"preview": "import { ConnectedRouter } from 'connected-react-router/immutable';\nimport { History } from 'history';\nimport React from"
},
{
"path": "frontend/src/components/ContentLoader/__tests__/__snapshots__/contentLoader.spec.tsx.snap",
"chars": 1044,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`BodyContentLoader should renders correctly 1`] = `\n<div\n className"
},
{
"path": "frontend/src/components/ContentLoader/__tests__/contentLoader.spec.tsx",
"chars": 1028,
"preview": "import React from 'react';\nimport ShallowRenderer from 'react-test-renderer/shallow';\n\nimport { BodyContentLoader, Foote"
},
{
"path": "frontend/src/components/ContentLoader/contentLoader.scss",
"chars": 39,
"preview": ".bodyContentLoader {\n margin: auto;\n}\n"
},
{
"path": "frontend/src/components/ContentLoader/contentLoader.tsx",
"chars": 1018,
"preview": "import React from 'react';\nimport ContentLoader from 'react-content-loader';\n\nconst styles = require('./contentLoader.sc"
},
{
"path": "frontend/src/components/ContentLoader/index.tsx",
"chars": 95,
"preview": "export { BodyContentLoader, HeaderContentLoader, FooterContentLoader } from './contentLoader';\n"
},
{
"path": "frontend/src/components/Dropdown/__tests__/__snapshots__/dropdown.spec.tsx.snap",
"chars": 306,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Dropdown should renders correctly 1`] = `\nArray [\n <ul\n classNa"
},
{
"path": "frontend/src/components/Dropdown/__tests__/dropdown.spec.tsx",
"chars": 546,
"preview": "import 'materialize-css';\nimport React, { Fragment } from 'react';\nimport { BrowserRouter } from 'react-router-dom';\nimp"
},
{
"path": "frontend/src/components/Dropdown/dropdown.tsx",
"chars": 858,
"preview": "import React from 'react';\nimport { Translation } from 'react-i18next';\nimport { Link } from 'react-router-dom';\n\ninterf"
},
{
"path": "frontend/src/components/Dropdown/index.tsx",
"chars": 38,
"preview": "export { default } from './dropdown';\n"
},
{
"path": "frontend/src/components/Footer/__tests__/__snapshots__/footer.spec.tsx.snap",
"chars": 1793,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Footer should renders correctly 1`] = `\n<footer\n className=\"page-f"
},
{
"path": "frontend/src/components/Footer/__tests__/footer.spec.tsx",
"chars": 322,
"preview": "import React from 'react';\nimport TestRenderer from 'react-test-renderer';\n\nimport { Footer } from '../footer';\n\ndescrib"
},
{
"path": "frontend/src/components/Footer/footer.tsx",
"chars": 1631,
"preview": "import React from 'react';\nimport { Translation } from 'react-i18next';\n\nexport class Footer extends React.Component {\n "
},
{
"path": "frontend/src/components/Footer/index.tsx",
"chars": 36,
"preview": "export { default } from './footer';\n"
},
{
"path": "frontend/src/components/Header/__tests__/__snapshots__/header.spec.tsx.snap",
"chars": 2953,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Header should renders correctly 1`] = `\nArray [\n <div>\n <nav>\n "
},
{
"path": "frontend/src/components/Header/__tests__/header.spec.tsx",
"chars": 575,
"preview": "import 'materialize-css';\nimport React, { Fragment } from 'react';\nimport { BrowserRouter } from 'react-router-dom';\nimp"
},
{
"path": "frontend/src/components/Header/header.scss",
"chars": 51,
"preview": ".logo {\n margin: 6px 0 0 0;\n max-height: 50px;\n}\n"
},
{
"path": "frontend/src/components/Header/header.tsx",
"chars": 3129,
"preview": "import React from 'react';\nimport { Translation } from 'react-i18next';\nimport { connect } from 'react-redux';\nimport { "
},
{
"path": "frontend/src/components/Header/index.tsx",
"chars": 36,
"preview": "export { default } from './header';\n"
},
{
"path": "frontend/src/i18n/index.tsx",
"chars": 714,
"preview": "import i18n from 'i18next';\nimport detector from 'i18next-browser-languagedetector';\nimport backend from 'i18next-xhr-ba"
},
{
"path": "frontend/src/index.tsx",
"chars": 1239,
"preview": "import { createBrowserHistory } from 'history';\nimport OfflinePluginRuntime from 'offline-plugin/runtime';\nimport React "
},
{
"path": "frontend/src/pages/HomePage/__tests__/__snapshots__/homePage.spec.tsx.snap",
"chars": 664,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`HomePage should renders correctly 1`] = `\nArray [\n <div\n classN"
},
{
"path": "frontend/src/pages/HomePage/__tests__/homePage.spec.tsx",
"chars": 538,
"preview": "import 'materialize-css';\nimport React from 'react';\nimport TestRenderer from 'react-test-renderer';\n\nimport { HomePage "
},
{
"path": "frontend/src/pages/HomePage/components/Carousel/__tests__/__snapshots__/carousel.spec.tsx.snap",
"chars": 1174,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Carousel should renders correctly 1`] = `\n<div\n className=\"carouse"
},
{
"path": "frontend/src/pages/HomePage/components/Carousel/__tests__/carousel.spec.tsx",
"chars": 356,
"preview": "import 'materialize-css';\nimport React from 'react';\nimport TestRenderer from 'react-test-renderer';\n\nimport { Carousel "
},
{
"path": "frontend/src/pages/HomePage/components/Carousel/carousel.tsx",
"chars": 3080,
"preview": "import React from 'react';\nimport { Translation } from 'react-i18next';\n\nimport { CAROUSEL_AUTOPLAY_INTERVAL, TOAST_DISP"
},
{
"path": "frontend/src/pages/HomePage/components/Carousel/constants/carousel.tsx",
"chars": 194,
"preview": "// Toast timer\nexport const TOAST_DISPLAY_DURATION = 3000;\n\n// Tooltip timer\nexport const TOOLTIP_DELAY_TIME = 50;\n\n// C"
},
{
"path": "frontend/src/pages/HomePage/components/Carousel/index.tsx",
"chars": 38,
"preview": "export { default } from './carousel';\n"
},
{
"path": "frontend/src/pages/HomePage/components/Pushpin/__tests__/__snapshots__/pushpin.spec.tsx.snap",
"chars": 854,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Pushpin should renders correctly 1`] = `\n<div\n className=\"pushpin "
},
{
"path": "frontend/src/pages/HomePage/components/Pushpin/__tests__/pushpin.spec.tsx",
"chars": 351,
"preview": "import React from 'react';\nimport TestRenderer from 'react-test-renderer';\n\nimport { Pushpin } from '../pushpin';\n\ndescr"
},
{
"path": "frontend/src/pages/HomePage/components/Pushpin/index.tsx",
"chars": 37,
"preview": "export { default } from './pushpin';\n"
},
{
"path": "frontend/src/pages/HomePage/components/Pushpin/pushpin.scss",
"chars": 30,
"preview": ".pushpin {\n height: 825px;\n}\n"
},
{
"path": "frontend/src/pages/HomePage/components/Pushpin/pushpin.tsx",
"chars": 1168,
"preview": "import React from 'react';\nimport { Translation } from 'react-i18next';\n\nconst styles = require('./pushpin.scss');\n\ninte"
},
{
"path": "frontend/src/pages/HomePage/components/TranslationButton/__tests__/__snapshots__/translationButton.spec.tsx.snap",
"chars": 698,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`TranslationButton should renders correctly 1`] = `\n<div\n className"
},
{
"path": "frontend/src/pages/HomePage/components/TranslationButton/__tests__/translationButton.spec.tsx",
"chars": 392,
"preview": "import 'materialize-css';\nimport React from 'react';\nimport TestRenderer from 'react-test-renderer';\n\nimport { Translati"
},
{
"path": "frontend/src/pages/HomePage/components/TranslationButton/index.tsx",
"chars": 47,
"preview": "export { default } from './translationButton';\n"
},
{
"path": "frontend/src/pages/HomePage/components/TranslationButton/translationButton.tsx",
"chars": 1321,
"preview": "import i18next from 'i18next';\nimport React from 'react';\nimport { Translation } from 'react-i18next';\n\nconst floatingAc"
},
{
"path": "frontend/src/pages/HomePage/homePage.scss",
"chars": 38,
"preview": ".home-page-block {\n height: 820px;\n}\n"
},
{
"path": "frontend/src/pages/HomePage/homePage.tsx",
"chars": 1807,
"preview": "import React, { Fragment } from 'react';\nimport { Translation } from 'react-i18next';\n\nimport Carousel from './component"
},
{
"path": "frontend/src/pages/HomePage/index.tsx",
"chars": 38,
"preview": "export { default } from './homePage';\n"
},
{
"path": "frontend/src/pages/NotFoundPage/__tests__/__snapshots__/notFoundPage.spec.tsx.snap",
"chars": 348,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`NotFoundPage should renders correctly 1`] = `\nArray [\n <h1\n cla"
},
{
"path": "frontend/src/pages/NotFoundPage/__tests__/notFoundPage.spec.tsx",
"chars": 346,
"preview": "import React from 'react';\nimport TestRenderer from 'react-test-renderer';\n\nimport { NotFoundPage } from '../notFoundPag"
},
{
"path": "frontend/src/pages/NotFoundPage/index.tsx",
"chars": 42,
"preview": "export { default } from './notFoundPage';\n"
},
{
"path": "frontend/src/pages/NotFoundPage/notFoundPage.scss",
"chars": 254,
"preview": "@import 'sass/variables';\n\n.not-found-img {\n display: block;\n margin: auto;\n padding: 60px;\n max-width: 90%;\n objec"
},
{
"path": "frontend/src/pages/NotFoundPage/notFoundPage.tsx",
"chars": 1154,
"preview": "import React, { Fragment } from 'react';\nimport { Translation } from 'react-i18next';\n\nconst styles = require('./notFoun"
},
{
"path": "frontend/src/pages/ParallaxPage/__tests__/__snapshots__/parallaxPage.spec.tsx.snap",
"chars": 1274,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`ParallaxPage should renders correctly 1`] = `\nArray [\n <div\n cl"
},
{
"path": "frontend/src/pages/ParallaxPage/__tests__/parallaxPage.spec.tsx",
"chars": 432,
"preview": "import 'materialize-css';\nimport React from 'react';\nimport TestRenderer from 'react-test-renderer';\n\nimport { ParallaxP"
},
{
"path": "frontend/src/pages/ParallaxPage/components/PrismCodes/__tests__/__snapshots__/prismCodes.spec.tsx.snap",
"chars": 1264,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`PrismCodes component 1`] = `\n<pre\n className=\"language-tsx\"\n>\n <c"
},
{
"path": "frontend/src/pages/ParallaxPage/components/PrismCodes/__tests__/prismCodes.spec.tsx",
"chars": 498,
"preview": "import React from 'react';\nimport renderer from 'react-test-renderer';\n\nimport { PARALLAX_CODE } from '../constants/pris"
},
{
"path": "frontend/src/pages/ParallaxPage/components/PrismCodes/constants/prismCodes.tsx",
"chars": 944,
"preview": "// Code snippets\nexport const PARALLAX_CODE = `\n<div>\n <div className='white'>\n <h1 className='page-title'>{t('title"
},
{
"path": "frontend/src/pages/ParallaxPage/components/PrismCodes/index.tsx",
"chars": 80,
"preview": "export { default } from './prismCodes';\nexport * from './constants/prismCodes';\n"
},
{
"path": "frontend/src/pages/ParallaxPage/components/PrismCodes/prismCodes.scss",
"chars": 492,
"preview": "code,\npre {\n position: relative;\n font-size: 1em;\n}\n\npre[class*=\"language-\"] {\n padding: 20px 22px;\n border: solid 1"
},
{
"path": "frontend/src/pages/ParallaxPage/components/PrismCodes/prismCodes.tsx",
"chars": 527,
"preview": "import React from 'react';\n\nimport { highlightAll } from 'prismjs';\nimport 'prismjs/themes/prism.css';\nimport './prismCo"
},
{
"path": "frontend/src/pages/ParallaxPage/index.tsx",
"chars": 42,
"preview": "export { default } from './parallaxPage';\n"
},
{
"path": "frontend/src/pages/ParallaxPage/parallaxPage.scss",
"chars": 93,
"preview": "@import 'sass/variables';\n\n.parallax-header {\n color: $primary-color;\n font-weight: 300;\n}\n"
},
{
"path": "frontend/src/pages/ParallaxPage/parallaxPage.tsx",
"chars": 1716,
"preview": "import React, { Fragment } from 'react';\nimport { Translation } from 'react-i18next';\n\nimport PrismCodes, { PARALLAX_COD"
},
{
"path": "frontend/src/pages/ReactPage/__tests__/__snapshots__/reactPage.spec.tsx.snap",
"chars": 266,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`ReactPage should renders correctly 1`] = `\n<div>\n <div\n classNa"
},
{
"path": "frontend/src/pages/ReactPage/__tests__/reactPage.spec.tsx",
"chars": 451,
"preview": "import React from 'react';\nimport TestRenderer from 'react-test-renderer';\n\nimport { ReactPage } from '../reactPage';\n\nj"
},
{
"path": "frontend/src/pages/ReactPage/components/FetchNote/__tests__/__snapshots__/fetchNote.spec.tsx.snap",
"chars": 454,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`FetchNote should renders correctly 1`] = `\n<div\n className=\"center"
},
{
"path": "frontend/src/pages/ReactPage/components/FetchNote/__tests__/fetchNote.spec.tsx",
"chars": 696,
"preview": "import { List } from 'immutable';\nimport 'materialize-css';\nimport React from 'react';\nimport TestRenderer from 'react-t"
},
{
"path": "frontend/src/pages/ReactPage/components/FetchNote/fetchNote.scss",
"chars": 421,
"preview": "@import 'sass/variables';\n@import '../../reactPage';\n\n.fetch-note-layout {\n @extend .react-block;\n}\n\n.fetch-note-title "
},
{
"path": "frontend/src/pages/ReactPage/components/FetchNote/fetchNote.tsx",
"chars": 3195,
"preview": "import { List } from 'immutable';\nimport React from 'react';\nimport { Translation } from 'react-i18next';\nimport { conne"
},
{
"path": "frontend/src/pages/ReactPage/components/FetchNote/index.tsx",
"chars": 39,
"preview": "export { default } from './fetchNote';\n"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/__tests__/__snapshots__/todoLayout.spec.tsx.snap",
"chars": 270,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`TodoLayout should renders correctly 1`] = `\n<div\n className=\"cente"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/__tests__/todoLayout.spec.tsx",
"chars": 510,
"preview": "import React from 'react';\nimport TestRenderer from 'react-test-renderer';\n\nimport { TodoLayout } from '../todoLayout';\n"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/__tests__/__snapshots__/todoFooter.spec.tsx.snap",
"chars": 416,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`TodoFooter should renders correctly 1`] = `\n<div\n className=\"todo-"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/__tests__/todoFooter.spec.tsx",
"chars": 398,
"preview": "import React from 'react';\nimport TestRenderer from 'react-test-renderer';\n\nimport { TodoFooter } from '../todoFooter';\n"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/components/TodoFilter/__tests__/__snapshots__/todoFilter.spec.tsx.snap",
"chars": 210,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`TodoFilter should renders correctly 1`] = `\n<a\n className=\"btn wav"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/components/TodoFilter/__tests__/todoFilter.spec.tsx",
"chars": 480,
"preview": "import React from 'react';\nimport TestRenderer from 'react-test-renderer';\n\nimport { TodoFilter } from '../todoFilter';\n"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/components/TodoFilter/index.tsx",
"chars": 40,
"preview": "export { default } from './todoFilter';\n"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/components/TodoFilter/todoFilter.scss",
"chars": 271,
"preview": "@import 'sass/variables';\n\n.todo-filter-btn {\n width: 120px;\n text-align: center;\n margin: 3px;\n padding: 0;\n}\n\n@med"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/components/TodoFilter/todoFilter.tsx",
"chars": 1696,
"preview": "import React from 'react';\nimport { connect } from 'react-redux';\nimport { AnyAction, Dispatch } from 'redux';\n\nimport {"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/index.tsx",
"chars": 40,
"preview": "export { default } from './todoFooter';\n"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoFooter/todoFooter.tsx",
"chars": 756,
"preview": "import React from 'react';\nimport { Translation } from 'react-i18next';\n\nimport TodoFilter from './components/TodoFilter"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoInput/__tests__/__snapshots__/todoInput.spec.tsx.snap",
"chars": 308,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`TodoInput should renders correctly 1`] = `\n<form\n onSubmit={[Funct"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoInput/__tests__/todoInput.spec.tsx",
"chars": 416,
"preview": "import React from 'react';\nimport TestRenderer from 'react-test-renderer';\n\nimport { TodoInput } from '../todoInput';\n\n/"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoInput/index.tsx",
"chars": 39,
"preview": "export { default } from './todoInput';\n"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoInput/todoInput.tsx",
"chars": 1408,
"preview": "import React, { Fragment } from 'react';\nimport { Translation } from 'react-i18next';\nimport { connect } from 'react-red"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/__tests__/__snapshots__/todoList.spec.tsx.snap",
"chars": 101,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`TodoList should renders correctly 1`] = `null`;\n"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/__tests__/todoList.spec.tsx",
"chars": 516,
"preview": "import { List } from 'immutable';\nimport React from 'react';\nimport TestRenderer from 'react-test-renderer';\n\nimport { T"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/components/Todo/__tests__/__snapshots__/todo.spec.tsx.snap",
"chars": 263,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Todo should renders correctly 1`] = `\n<a\n className=\"collection-it"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/components/Todo/__tests__/todo.spec.tsx",
"chars": 487,
"preview": "import React from 'react';\nimport TestRenderer from 'react-test-renderer';\n\nimport Todo from '../todo';\n\n// Mock dispatc"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/components/Todo/index.tsx",
"chars": 34,
"preview": "export { default } from './todo';\n"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/components/Todo/todo.scss",
"chars": 116,
"preview": ".todo-completed {\n text-decoration: line-through;\n color: grey;\n}\n\n.todo-incompleted {\n text-decoration: none;\n}\n"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/components/Todo/todo.tsx",
"chars": 949,
"preview": "import React from 'react';\n\nimport { ITodo } from 'services/todos/types';\n\nconst styles = require('./todo.scss');\n\ninter"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/index.tsx",
"chars": 38,
"preview": "export { default } from './todoList';\n"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/components/TodoList/todoList.tsx",
"chars": 2056,
"preview": "import { List } from 'immutable';\nimport React from 'react';\nimport { connect } from 'react-redux';\nimport { AnyAction, "
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/index.tsx",
"chars": 40,
"preview": "export { default } from './todoLayout';\n"
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/todoLayout.scss",
"chars": 142,
"preview": "@import 'sass/variables';\n@import '../../reactPage';\n\n.todo-layout {\n @extend .react-block;\n}\n\n.todo-title {\n @extend "
},
{
"path": "frontend/src/pages/ReactPage/components/TodoLayout/todoLayout.tsx",
"chars": 717,
"preview": "import React from 'react';\nimport { Translation } from 'react-i18next';\n\nimport TodoFooter from './components/TodoFooter"
},
{
"path": "frontend/src/pages/ReactPage/index.tsx",
"chars": 39,
"preview": "export { default } from './reactPage';\n"
},
{
"path": "frontend/src/pages/ReactPage/reactPage.scss",
"chars": 713,
"preview": "@import 'sass/variables';\n\n.react-block {\n border: 1px lightgray solid;\n margin: 50px auto;\n width: 850px;\n padding:"
},
{
"path": "frontend/src/pages/ReactPage/reactPage.tsx",
"chars": 593,
"preview": "import React from 'react';\nimport { Translation } from 'react-i18next';\n\nimport FetchNote from './components/FetchNote';"
},
{
"path": "frontend/src/reducers/index.tsx",
"chars": 464,
"preview": "import { connectRouter } from 'connected-react-router/immutable';\nimport { History } from 'history';\nimport { combineRed"
},
{
"path": "frontend/src/router.tsx",
"chars": 1403,
"preview": "import React, { Fragment, lazy, Suspense } from 'react';\nimport { Route, Switch } from 'react-router';\n\nimport 'material"
},
{
"path": "frontend/src/sagas/index.tsx",
"chars": 156,
"preview": "import { all } from 'redux-saga/effects';\n\nimport notes from 'services/notes/sagas';\n\nexport default function* sagas() {"
},
{
"path": "frontend/src/sass/global.scss",
"chars": 530,
"preview": "@import \"variables\";\n\n:global {\n @import \"~materialize-css/sass/materialize\";\n\n .container {\n height: 100%;\n }\n\n "
},
{
"path": "frontend/src/sass/variables.scss",
"chars": 278,
"preview": "// This file override materialize css variables\n// https://github.com/Dogfalo/materialize/blob/master/sass/components/_v"
},
{
"path": "frontend/src/services/notes/actions.tsx",
"chars": 1074,
"preview": "import {ADD_NOTE_REQUESTED, EDIT_NOTE_REQUESTED, FETCH_ALL_NOTES_REQUESTED, FETCH_NOTE_REQUESTED, REMOVE_NOTE_REQUESTED "
},
{
"path": "frontend/src/services/notes/apis.tsx",
"chars": 2203,
"preview": "import axios from 'axios';\n\nimport { IActionAddNoteRequested, IActionEditNoteRequested, IActionFetchNoteRequested, IActi"
},
{
"path": "frontend/src/services/notes/constants.tsx",
"chars": 1664,
"preview": "import { IAsyncCall } from 'types/global';\n\nexport const FETCH_ALL_NOTES_REQUESTED = 'FETCH_ALL_NOTES/REQUESTED';\nexport"
},
{
"path": "frontend/src/services/notes/reducer.tsx",
"chars": 1161,
"preview": "import { List, Record } from 'immutable';\n\nimport { FETCH_ALL_NOTES_FAILURE, FETCH_ALL_NOTES_REQUESTED, FETCH_ALL_NOTES_"
},
{
"path": "frontend/src/services/notes/sagas.tsx",
"chars": 1119,
"preview": "import { all, call, put, takeEvery } from 'redux-saga/effects';\n\nimport { IAsyncCall } from 'types/global';\nimport Notes"
},
{
"path": "frontend/src/services/notes/types.d.ts",
"chars": 2887,
"preview": "import { List, Record } from 'immutable';\n\nimport { ADD_NOTE_FAILURE, ADD_NOTE_REQUESTED, ADD_NOTE_SUCCESS, EDIT_NOTE_FA"
},
{
"path": "frontend/src/services/todos/__test__/actions.spec.tsx",
"chars": 1995,
"preview": "import { addTodo, setVisibilityFilter, toggleTodo } from '../actions';\nimport { ADD_TODO, SET_VISIBILITY_FILTER, TOGGLE_"
},
{
"path": "frontend/src/services/todos/__test__/reducer.spec.tsx",
"chars": 4237,
"preview": "import { List } from 'immutable';\n\nimport { ADD_TODO, SET_VISIBILITY_FILTER, TOGGLE_TODO, VISIBILITY_FILTER_OPTIONS } fr"
},
{
"path": "frontend/src/services/todos/actions.tsx",
"chars": 544,
"preview": "import { v4 } from 'uuid';\n\nimport { ADD_TODO, SET_VISIBILITY_FILTER, TOGGLE_TODO } from './constants';\nimport { IAction"
},
{
"path": "frontend/src/services/todos/constants.tsx",
"chars": 355,
"preview": "// Todo action types\nexport const ADD_TODO = 'ADD_TODO';\nexport const TOGGLE_TODO = 'TOGGLE_TODO';\n\n// Visibility action"
},
{
"path": "frontend/src/services/todos/reducer.tsx",
"chars": 1376,
"preview": "import { List, Record } from 'immutable';\n\nimport { ADD_TODO, SET_VISIBILITY_FILTER, TOGGLE_TODO, VISIBILITY_FILTER_OPTI"
},
{
"path": "frontend/src/services/todos/types.d.ts",
"chars": 791,
"preview": "import { List, Record } from 'immutable';\n\nimport { ADD_TODO, SET_VISIBILITY_FILTER, TOGGLE_TODO } from './constants';\n\n"
},
{
"path": "frontend/src/store/index.tsx",
"chars": 1303,
"preview": "import { routerMiddleware } from 'connected-react-router/immutable';\nimport { History } from 'history';\nimport Immutable"
},
{
"path": "frontend/src/types/global.d.ts",
"chars": 591,
"preview": "import { RouterState } from 'connected-react-router/immutable';\nimport { Record } from 'immutable';\n\nimport { INotesStat"
},
{
"path": "frontend/src/utils/index.tsx",
"chars": 67,
"preview": "export const isProduction = process.env.NODE_ENV === 'production';\n"
},
{
"path": "package.json",
"chars": 6378,
"preview": "{\n \"name\": \"express-webpack-react-redux-typescript-boilerplate\",\n \"version\": \"1.0.0\",\n \"repository\": {\n \"type\": \"g"
},
{
"path": "tsconfig.json",
"chars": 436,
"preview": "{\n \"compileOnSave\": false,\n \"compilerOptions\": {\n \"baseUrl\": \"frontend/src\",\n \"target\": \"es6\",\n \"module\": \"es"
},
{
"path": "tslint.json",
"chars": 829,
"preview": "{\n \"extends\": [\n \"tslint:recommended\",\n \"tslint-react\"\n ],\n \"rules\": {\n \"jsx-boolean-value\": false,\n \"jsx"
},
{
"path": "webpack.config.base.babel.js",
"chars": 5709,
"preview": "import 'dotenv/config'; // Allow webpack config file to use .env variables\n\nimport path from 'path';\nimport webpack from"
},
{
"path": "webpack.config.dev.babel.js",
"chars": 1333,
"preview": "import path from 'path';\nimport merge from 'webpack-merge';\n\nimport BaseWebpackConfig from './webpack.config.base.babel'"
},
{
"path": "webpack.config.dll.babel.js",
"chars": 1873,
"preview": "import path from 'path';\nimport webpack from 'webpack';\n\nimport ProgressBarWebpackPlugin from 'progress-bar-webpack-plug"
},
{
"path": "webpack.config.prod.babel.js",
"chars": 1177,
"preview": "import path from 'path';\nimport merge from 'webpack-merge';\n\nimport OfflinePlugin from 'offline-plugin';\n\nimport BaseWeb"
},
{
"path": "webpack.config.profile.babel.js",
"chars": 404,
"preview": "import merge from 'webpack-merge';\n\nimport { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';\n\nimport ProdWebpackC"
}
]
About this extraction
This page contains the full source code of the Armour/express-webpack-react-redux-typescript-boilerplate GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 174 files (146.8 KB), approximately 45.3k tokens, and a symbol index with 131 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.