Repository: piotrwitek/react-redux-typescript-realworld-app
Branch: master
Commit: f92c56a61265
Files: 61
Total size: 49.5 KB
Directory structure:
gitextract_6ug6f53y/
├── .eslintrc
├── .gitignore
├── .prettierrc
├── .vscode/
│ └── settings.json
├── CODE_OF_CONDUCT.md
├── README.md
├── lambdas/
│ ├── .babelrc
│ ├── build/
│ │ └── hello.js
│ └── src/
│ └── hello.ts
├── netlify.toml
├── package.json
├── public/
│ ├── index.html
│ └── manifest.json
├── server/
│ ├── package.json
│ ├── src/
│ │ └── env.ts
│ └── tsconfig.json
├── src/
│ ├── App.test.tsx
│ ├── App.tsx
│ ├── components/
│ │ ├── BackLink.tsx
│ │ ├── FlexBox.tsx
│ │ ├── FlexColumn.tsx
│ │ └── FlexRow.tsx
│ ├── features/
│ │ ├── app/
│ │ │ └── epics.ts
│ │ └── articles/
│ │ ├── actions.ts
│ │ ├── components/
│ │ │ ├── ArticleActionsMenu.tsx
│ │ │ ├── ArticleForm.tsx
│ │ │ ├── ArticleList.tsx
│ │ │ ├── ArticleListItem.tsx
│ │ │ └── ArticleView.tsx
│ │ ├── epics.ts
│ │ ├── reducer.ts
│ │ ├── selectors.ts
│ │ └── types.d.ts
│ ├── index.css
│ ├── index.tsx
│ ├── layouts/
│ │ ├── Main.css
│ │ └── Main.tsx
│ ├── react-app-env.d.ts
│ ├── router-paths.ts
│ ├── routes/
│ │ ├── AddArticle.tsx
│ │ ├── EditArticle.tsx
│ │ ├── Home.tsx
│ │ └── ViewArticle.tsx
│ ├── serviceWorker.ts
│ ├── services/
│ │ ├── articles-api-client.ts
│ │ ├── index.ts
│ │ ├── local-storage-service.ts
│ │ ├── logger-service.ts
│ │ ├── toast-service.ts
│ │ └── types.d.ts
│ └── store/
│ ├── index.ts
│ ├── root-action.ts
│ ├── root-epic.ts
│ ├── root-reducer.ts
│ ├── types.d.ts
│ └── utils.ts
├── tsconfig.json
├── tsconfig.test.json
└── typings/
├── augmentations.d.ts
├── globals.d.ts
└── modules.d.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc
================================================
{
"extends": [
"react-app",
"./node_modules/react-redux-typescript-scripts/eslint.js"
]
}
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/server/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
/server/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
================================================
FILE: .prettierrc
================================================
{
"printWidth": 80,
"semi": true,
"singleQuote": true,
"trailingComma": "es5"
}
================================================
FILE: .vscode/settings.json
================================================
{
"typescript.tsdk": "node_modules/typescript/lib"
}
================================================
FILE: 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, sex characteristics, 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 piotrek.witek@gmail.com. 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
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
================================================
FILE: README.md
================================================
<div align="center">
# React, Redux, TypeScript - RealWorld App
## 🚧🚧🚧 UNDER CONSTRUCTION 🚧🚧🚧
### **LIVE DEMO: [LINK](https://react-redux-typescript-realworld-app.netlify.com/)**
_Reference implementation of RealWorld [JAMStack](https://jamstack.org/) Application based on ["React, Redux, TypeScript Guide"](https://github.com/piotrwitek/react-redux-typescript-guide)
and [Create React App v3.0](https://facebook.github.io/create-react-app/)._
</div>
---
## Features Roadmap:
- [x] Routing with React-Router
- [ ] User Identity
- [ ] External providers (Google, Github, Bitbucket)
- [ ] Registration / Authentication
- [x] Cross-cutting Application Services
- [x] Local Storage
- [x] Client Logger
- [x] Toasts
- [ ] Analytics
- [x] Feature Folders
- [x] `/articles` - Articles listing with CRUD Operations
- [ ] `/realtime-monitoring` - Realtime monitoring of connected users using Websockets
- [x] REST API Integration (API Client)
- [ ] WebSockets Integration
- [ ] Serverless Lambda Functions (Netlify Functions)
- [ ] Utilities (HOC, Hooks, Media Queries...)
- [ ] Typesafe Styling/Theming with CSSinJS (`Emotion`)
- [ ] ...
---
## Available Scripts
### `npm start`
Runs the app in the development modeat [http://localhost:3000](http://localhost:3000)
### `npm test`
Launches the test runner in the interactive watch mode.<br>
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.<br>
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
## Learn More
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
================================================
FILE: lambdas/.babelrc
================================================
// lambda build config
{
"presets": ["@babel/preset-typescript", "@babel/preset-env"],
"plugins": [
"@babel/transform-runtime",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-object-assign",
"@babel/plugin-proposal-object-rest-spread"
]
}
================================================
FILE: lambdas/build/hello.js
================================================
!function(t,r){for(var e in r)t[e]=r[e]}(exports,function(t){var r={};function e(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{}};return t[n].call(o.exports,o,o.exports,e),o.l=!0,o.exports}return e.m=t,e.c=r,e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{enumerable:!0,get:n})},e.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},e.t=function(t,r){if(1&r&&(t=e(t)),8&r)return t;if(4&r&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(e.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&r&&"string"!=typeof t)for(var o in t)e.d(n,o,function(r){return t[r]}.bind(null,o));return n},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,r){return Object.prototype.hasOwnProperty.call(t,r)},e.p="",e(e.s=3)}([function(t,r,e){t.exports=e(4)},function(t,r){function e(t,r,e,n,o,i,a){try{var u=t[i](a),c=u.value}catch(t){return void e(t)}u.done?r(c):Promise.resolve(c).then(n,o)}t.exports=function(t){return function(){var r=this,n=arguments;return new Promise(function(o,i){var a=t.apply(r,n);function u(t){e(a,o,i,u,c,"next",t)}function c(t){e(a,o,i,u,c,"throw",t)}u(void 0)})}}},function(t,r){t.exports=require("querystring")},function(t,r,e){"use strict";e.r(r),e.d(r,"handler",function(){return f});var n=e(0),o=e.n(n),i=e(1),a=e.n(i),u=e(2),c=e.n(u),f=function(){var t=a()(o.a.mark(function t(r,e){var n,i;return o.a.wrap(function(t){for(;;)switch(t.prev=t.next){case 0:t.t0=r.httpMethod,t.next="GET"===t.t0?3:"POST"===t.t0?4:7;break;case 3:return t.abrupt("return",{statusCode:200,body:"Hello, World!"});case 4:return n=c.a.parse(r.body),i=n.name||"World!",t.abrupt("return",{statusCode:200,body:"Hello, ".concat(i)});case 7:return t.abrupt("return",{statusCode:405,body:"Method Not Allowed"});case 8:case"end":return t.stop()}},t)}));return function(r,e){return t.apply(this,arguments)}}()},function(t,r,e){var n=function(t){"use strict";var r,e=Object.prototype,n=e.hasOwnProperty,o="function"==typeof Symbol?Symbol:{},i=o.iterator||"@@iterator",a=o.asyncIterator||"@@asyncIterator",u=o.toStringTag||"@@toStringTag";function c(t,r,e,n){var o=r&&r.prototype instanceof d?r:d,i=Object.create(o.prototype),a=new P(n||[]);return i._invoke=function(t,r,e){var n=s;return function(o,i){if(n===h)throw new Error("Generator is already running");if(n===p){if("throw"===o)throw i;return k()}for(e.method=o,e.arg=i;;){var a=e.delegate;if(a){var u=_(a,e);if(u){if(u===y)continue;return u}}if("next"===e.method)e.sent=e._sent=e.arg;else if("throw"===e.method){if(n===s)throw n=p,e.arg;e.dispatchException(e.arg)}else"return"===e.method&&e.abrupt("return",e.arg);n=h;var c=f(t,r,e);if("normal"===c.type){if(n=e.done?p:l,c.arg===y)continue;return{value:c.arg,done:e.done}}"throw"===c.type&&(n=p,e.method="throw",e.arg=c.arg)}}}(t,e,a),i}function f(t,r,e){try{return{type:"normal",arg:t.call(r,e)}}catch(t){return{type:"throw",arg:t}}}t.wrap=c;var s="suspendedStart",l="suspendedYield",h="executing",p="completed",y={};function d(){}function v(){}function g(){}var m={};m[i]=function(){return this};var w=Object.getPrototypeOf,b=w&&w(w(S([])));b&&b!==e&&n.call(b,i)&&(m=b);var x=g.prototype=d.prototype=Object.create(m);function L(t){["next","throw","return"].forEach(function(r){t[r]=function(t){return this._invoke(r,t)}})}function E(t){var r;this._invoke=function(e,o){function i(){return new Promise(function(r,i){!function r(e,o,i,a){var u=f(t[e],t,o);if("throw"!==u.type){var c=u.arg,s=c.value;return s&&"object"==typeof s&&n.call(s,"__await")?Promise.resolve(s.__await).then(function(t){r("next",t,i,a)},function(t){r("throw",t,i,a)}):Promise.resolve(s).then(function(t){c.value=t,i(c)},function(t){return r("throw",t,i,a)})}a(u.arg)}(e,o,r,i)})}return r=r?r.then(i,i):i()}}function _(t,e){var n=t.iterator[e.method];if(n===r){if(e.delegate=null,"throw"===e.method){if(t.iterator.return&&(e.method="return",e.arg=r,_(t,e),"throw"===e.method))return y;e.method="throw",e.arg=new TypeError("The iterator does not provide a 'throw' method")}return y}var o=f(n,t.iterator,e.arg);if("throw"===o.type)return e.method="throw",e.arg=o.arg,e.delegate=null,y;var i=o.arg;return i?i.done?(e[t.resultName]=i.value,e.next=t.nextLoc,"return"!==e.method&&(e.method="next",e.arg=r),e.delegate=null,y):i:(e.method="throw",e.arg=new TypeError("iterator result is not an object"),e.delegate=null,y)}function O(t){var r={tryLoc:t[0]};1 in t&&(r.catchLoc=t[1]),2 in t&&(r.finallyLoc=t[2],r.afterLoc=t[3]),this.tryEntries.push(r)}function j(t){var r=t.completion||{};r.type="normal",delete r.arg,t.completion=r}function P(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(O,this),this.reset(!0)}function S(t){if(t){var e=t[i];if(e)return e.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var o=-1,a=function e(){for(;++o<t.length;)if(n.call(t,o))return e.value=t[o],e.done=!1,e;return e.value=r,e.done=!0,e};return a.next=a}}return{next:k}}function k(){return{value:r,done:!0}}return v.prototype=x.constructor=g,g.constructor=v,g[u]=v.displayName="GeneratorFunction",t.isGeneratorFunction=function(t){var r="function"==typeof t&&t.constructor;return!!r&&(r===v||"GeneratorFunction"===(r.displayName||r.name))},t.mark=function(t){return Object.setPrototypeOf?Object.setPrototypeOf(t,g):(t.__proto__=g,u in t||(t[u]="GeneratorFunction")),t.prototype=Object.create(x),t},t.awrap=function(t){return{__await:t}},L(E.prototype),E.prototype[a]=function(){return this},t.AsyncIterator=E,t.async=function(r,e,n,o){var i=new E(c(r,e,n,o));return t.isGeneratorFunction(e)?i:i.next().then(function(t){return t.done?t.value:i.next()})},L(x),x[u]="Generator",x[i]=function(){return this},x.toString=function(){return"[object Generator]"},t.keys=function(t){var r=[];for(var e in t)r.push(e);return r.reverse(),function e(){for(;r.length;){var n=r.pop();if(n in t)return e.value=n,e.done=!1,e}return e.done=!0,e}},t.values=S,P.prototype={constructor:P,reset:function(t){if(this.prev=0,this.next=0,this.sent=this._sent=r,this.done=!1,this.delegate=null,this.method="next",this.arg=r,this.tryEntries.forEach(j),!t)for(var e in this)"t"===e.charAt(0)&&n.call(this,e)&&!isNaN(+e.slice(1))&&(this[e]=r)},stop:function(){this.done=!0;var t=this.tryEntries[0].completion;if("throw"===t.type)throw t.arg;return this.rval},dispatchException:function(t){if(this.done)throw t;var e=this;function o(n,o){return u.type="throw",u.arg=t,e.next=n,o&&(e.method="next",e.arg=r),!!o}for(var i=this.tryEntries.length-1;i>=0;--i){var a=this.tryEntries[i],u=a.completion;if("root"===a.tryLoc)return o("end");if(a.tryLoc<=this.prev){var c=n.call(a,"catchLoc"),f=n.call(a,"finallyLoc");if(c&&f){if(this.prev<a.catchLoc)return o(a.catchLoc,!0);if(this.prev<a.finallyLoc)return o(a.finallyLoc)}else if(c){if(this.prev<a.catchLoc)return o(a.catchLoc,!0)}else{if(!f)throw new Error("try statement without catch or finally");if(this.prev<a.finallyLoc)return o(a.finallyLoc)}}}},abrupt:function(t,r){for(var e=this.tryEntries.length-1;e>=0;--e){var o=this.tryEntries[e];if(o.tryLoc<=this.prev&&n.call(o,"finallyLoc")&&this.prev<o.finallyLoc){var i=o;break}}i&&("break"===t||"continue"===t)&&i.tryLoc<=r&&r<=i.finallyLoc&&(i=null);var a=i?i.completion:{};return a.type=t,a.arg=r,i?(this.method="next",this.next=i.finallyLoc,y):this.complete(a)},complete:function(t,r){if("throw"===t.type)throw t.arg;return"break"===t.type||"continue"===t.type?this.next=t.arg:"return"===t.type?(this.rval=this.arg=t.arg,this.method="return",this.next="end"):"normal"===t.type&&r&&(this.next=r),y},finish:function(t){for(var r=this.tryEntries.length-1;r>=0;--r){var e=this.tryEntries[r];if(e.finallyLoc===t)return this.complete(e.completion,e.afterLoc),j(e),y}},catch:function(t){for(var r=this.tryEntries.length-1;r>=0;--r){var e=this.tryEntries[r];if(e.tryLoc===t){var n=e.completion;if("throw"===n.type){var o=n.arg;j(e)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,e,n){return this.delegate={iterator:S(t),resultName:e,nextLoc:n},"next"===this.method&&(this.arg=r),y}},t}(t.exports);try{regeneratorRuntime=n}catch(t){Function("r","regeneratorRuntime = r")(n)}}]));
================================================
FILE: lambdas/src/hello.ts
================================================
import { ALBHandler } from 'aws-lambda';
import querystring from 'querystring';
export const handler: ALBHandler = async (event, context) => {
switch (event.httpMethod) {
case 'GET': {
return {
statusCode: 200,
body: `Hello, World!`,
};
}
case 'POST': {
const params = querystring.parse(event.body!);
const name = params.name || 'World!';
return {
statusCode: 200,
body: `Hello, ${name}`,
};
}
default:
return { statusCode: 405, body: 'Method Not Allowed' };
}
};
================================================
FILE: netlify.toml
================================================
[build]
functions = "lambdas/build" # netlify-lambda reads this for local dev server
================================================
FILE: package.json
================================================
{
"name": "react-redux-typescript-realworld-app",
"description": "RealWorld App implementation based on \"react-redux-typescript-guide\"",
"version": "0.1.0",
"private": true,
"author": "Piotr Witek <piotrek.witek@gmail.com> (http://piotrwitek.github.io/)",
"repository": "https://github.com/piotrwitek/react-redux-typescript-realworld-app.git",
"homepage": "https://react-redux-typescript-realworld-app.netlify.com/",
"license": "MIT",
"main": "src/index.tsx",
"scripts": {
"start:client": "react-scripts start",
"start:lambdas": "netlify-lambda serve lambdas/src",
"start": "concurrently 'npm run start:client' 'npm run start:lambdas'",
"build:client": "react-scripts build",
"build:lambdas": "netlify-lambda build lambdas/src",
"build": "concurrently 'npm run build:client' 'npm run build:lambdas'",
"test": "react-scripts test",
"eject": "react-scripts eject",
"reinstall": "rm -rf ./node_modules && npm install",
"ci-check": "npm run prettier && npm run tsc && npm run test",
"prettier": "prettier --list-different 'src/**/*.ts' || (echo '\nPlease run the following command to fix:\nnpm run prettier:fix\n'; exit 1)",
"prettier:fix": "prettier --write 'src/**/*.ts'",
"tsc": "tsc -p ./ --noEmit",
"tsc:watch": "tsc -p ./ --noEmit -w",
"deploy": "openode deploy"
},
"dependencies": {
"@babel/polyfill": "7.4.3",
"@emotion/core": "10.0.10",
"@emotion/styled": "10.0.10",
"@types/aws-lambda": "8.10.24",
"@types/jest": "24.0.11",
"@types/node": "11.13.7",
"@types/prop-types": "15.7.1",
"@types/react": "16.8.14",
"@types/react-dom": "16.8.4",
"@types/react-redux": "7.0.8",
"@types/react-router-dom": "4.3.2",
"axios": "0.18.0",
"connected-react-router": "6.4.0",
"cuid": "2.1.6",
"fast-deep-equal": "2.0.1",
"formik": "1.5.2",
"netlify-lambda": "1.4.5",
"prettier": "1.17.0",
"prop-types": "15.7.2",
"react": "16.8.6",
"react-dom": "16.8.6",
"react-redux": "7.0.2",
"react-redux-typescript-scripts": "1.5.0",
"react-router-dom": "5.0.0",
"react-scripts": "3.0.0",
"react-testing-library": "6.1.2",
"react-toastify": "5.1.0",
"redux": "4.0.1",
"redux-observable": "1.1.0",
"reselect": "4.0.0",
"rxjs": "6.5.1",
"tslib": "1.9.3",
"typesafe-actions": "4.1.2",
"typescript": "3.4.5",
"utility-types": "3.5.0",
"yup": "0.27.0"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/yup": "0.26.12",
"concurrently": "4.1.0"
}
}
================================================
FILE: public/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</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 `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
================================================
FILE: public/manifest.json
================================================
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
================================================
FILE: server/package.json
================================================
{
"name": "server",
"version": "0.1.0",
"private": true,
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "...",
"build": "...",
"deploy": "openode deploy"
},
"author": "Piotrek Witek",
"license": "MIT",
"devDependencies": {
"openode": "2.0.3",
"react-redux-typescript-scripts": "1.5.0",
"typescript": "3.4.5"
},
"dependencies": {}
}
================================================
FILE: server/src/env.ts
================================================
export const DB_HOST = process.env.DB_HOST;
================================================
FILE: server/tsconfig.json
================================================
{
"include": ["src", "typings"],
"exclude": ["src/**/*.spec.*"],
"extends": "./node_modules/react-redux-typescript-scripts/tsconfig.json",
"compilerOptions": {}
}
================================================
FILE: src/App.test.tsx
================================================
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
================================================
FILE: src/App.tsx
================================================
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
import { Switch, Route } from 'react-router';
import store, { history } from './store';
import Home from './routes/Home';
import { getPath } from './router-paths';
import AddArticle from './routes/AddArticle';
import EditArticle from './routes/EditArticle';
import ViewArticle from './routes/ViewArticle';
class App extends Component {
render() {
return (
<Provider store={store}>
<ConnectedRouter history={history}>
<Switch>
<Route exact path={getPath('home')} render={Home} />
<Route exact path={getPath('addArticle')} render={AddArticle} />
<Route
exact
path={getPath('editArticle', ':articleId')}
render={props => <EditArticle {...props} />}
/>
<Route
exact
path={getPath('viewArticle', ':articleId')}
render={props => <ViewArticle {...props} />}
/>
<Route render={() => <div>Page not found!</div>} />
</Switch>
</ConnectedRouter>
</Provider>
);
}
}
export default App;
================================================
FILE: src/components/BackLink.tsx
================================================
import React from 'react';
import areEqual from 'fast-deep-equal';
import { Link } from 'react-router-dom';
interface Props {}
const BackLink = React.memo<Props>(() => {
return (
<Link to="/" className="link">
{'< Back'}
</Link>
);
}, areEqual);
export default BackLink;
================================================
FILE: src/components/FlexBox.tsx
================================================
import styled from '@emotion/styled/macro';
import { CSSObject } from '@emotion/core';
type Props = {
className?: string;
style?: React.CSSProperties;
/* @description will add spacing between children, work dependinng on row/column layout */
itemsSpacing?: number;
direction?: CSSObject['flexDirection'];
wrap?: CSSObject['flexWrap'];
justify?: CSSObject['justifyContent'];
align?: CSSObject['alignItems'];
grow?: CSSObject['flexGrow'];
shrink?: CSSObject['flexShrink'];
};
const FlexBox = styled('div')<Props>(
({
itemsSpacing,
direction: flexDirection,
justify: justifyContent,
wrap: flexWrap,
align: alignItems,
grow: flexGrow,
shrink: flexShrink,
}) => ({
display: 'flex',
...(itemsSpacing != null && {
'> * + *': {
[flexDirection === 'row' ? 'marginLeft' : 'marginTop']: itemsSpacing,
},
}),
flexDirection,
flexWrap,
justifyContent,
alignItems,
flexGrow,
flexShrink,
})
);
export default FlexBox as React.FC<Props>;
================================================
FILE: src/components/FlexColumn.tsx
================================================
import React from 'react';
import { CSSObject } from '@emotion/core';
import FlexBox from './FlexBox';
type Props = React.ComponentProps<typeof FlexBox> & {
direction?: CSSObject['flexDirection'];
};
export default (props: Props) => <FlexBox direction="column" {...props} />;
================================================
FILE: src/components/FlexRow.tsx
================================================
import React from 'react';
import { CSSObject } from '@emotion/core';
import FlexBox from './FlexBox';
type Props = React.ComponentProps<typeof FlexBox> & {
direction?: CSSObject['flexDirection'];
};
export default (props: Props) => <FlexBox direction="row" {...props} />;
================================================
FILE: src/features/app/epics.ts
================================================
import { RootEpic } from 'MyTypes';
import { tap, ignoreElements, filter, first, map } from 'rxjs/operators';
import { isActionOf } from 'typesafe-actions';
import {
loadArticlesAsync,
createArticleAsync,
updateArticleAsync,
deleteArticleAsync,
} from '../articles/actions';
export const persistArticlesInLocalStorage: RootEpic = (
action$,
store,
{ localStorage }
) =>
action$.pipe(
filter(
isActionOf([
loadArticlesAsync.success,
createArticleAsync.success,
updateArticleAsync.success,
deleteArticleAsync.success,
])
),
tap(_ => {
// handle side-effects
localStorage.set('articles', store.value.articles.articles);
}),
ignoreElements()
);
export const loadDataOnAppStart: RootEpic = (action$, store, { api }) =>
action$.pipe(
first(),
map(loadArticlesAsync.request)
);
================================================
FILE: src/features/articles/actions.ts
================================================
import { Article } from 'MyModels';
import { createAsyncAction } from 'typesafe-actions';
export const loadArticlesAsync = createAsyncAction(
'LOAD_ARTICLES_REQUEST',
'LOAD_ARTICLES_SUCCESS',
'LOAD_ARTICLES_FAILURE'
)<undefined, Article[], string>();
export const createArticleAsync = createAsyncAction(
'CREATE_ARTICLE_REQUEST',
'CREATE_ARTICLE_SUCCESS',
'CREATE_ARTICLE_FAILURE'
)<Article, Article[], string>();
export const updateArticleAsync = createAsyncAction(
'UPDATE_ARTICLE_REQUEST',
'UPDATE_ARTICLE_SUCCESS',
'UPDATE_ARTICLE_FAILURE'
)<Article, Article[], string>();
export const deleteArticleAsync = createAsyncAction(
'DELETE_ARTICLE_REQUEST',
'DELETE_ARTICLE_SUCCESS',
'DELETE_ARTICLE_FAILURE'
)<Article, Article[], Article>();
================================================
FILE: src/features/articles/components/ArticleActionsMenu.tsx
================================================
import { RootState } from 'MyTypes';
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { getPath } from '../../../router-paths';
const mapStateToProps = (state: RootState) => ({});
const dispatchProps = {};
type Props = ReturnType<typeof mapStateToProps> & typeof dispatchProps;
type State = {};
class ArticleActionsMenu extends React.Component<Props, State> {
render() {
return (
<section>
<Link to={getPath('addArticle')}>Create article</Link>
</section>
);
}
}
export default connect(
mapStateToProps,
dispatchProps
)(ArticleActionsMenu);
================================================
FILE: src/features/articles/components/ArticleForm.tsx
================================================
import React from 'react';
import cuid from 'cuid';
import { Form, FormikProps, Field, withFormik, ErrorMessage } from 'formik';
import { Article } from 'MyModels';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { push } from 'connected-react-router';
import { createArticleAsync, updateArticleAsync } from '../actions';
// import { getPath } from '../../../router-paths';
type FormValues = Pick<Article, 'title' | 'content'> & {};
const dispatchProps = {
createArticle: (values: FormValues) =>
createArticleAsync.request({
id: cuid(),
...values,
}),
updateArticle: (values: Article) =>
updateArticleAsync.request({
...values,
}),
redirectToListing: () => push('/'),
};
type Props = typeof dispatchProps & {
article?: Article;
};
const InnerForm: React.FC<Props & FormikProps<FormValues>> = props => {
const { isSubmitting, dirty } = props;
return (
<Form>
<div>
<label htmlFor="title">Title</label>
<br />
<Field
name="title"
placeholder="Title"
component="input"
type="text"
required
autoFocus
/>
<ErrorMessage name="title" />
</div>
<div>
<label htmlFor="title">Content</label>
<br />
<Field
name="content"
placeholder="Article content"
component="textarea"
required
type="text"
/>
<ErrorMessage name="content" />
</div>
<button type="submit" disabled={!dirty || isSubmitting}>
Submit
</button>
</Form>
);
};
export default compose(
connect(
null,
dispatchProps
),
withFormik<Props, FormValues>({
enableReinitialize: true,
// initialize values
mapPropsToValues: ({ article: data }) => ({
title: (data && data.title) || '',
content: (data && data.content) || '',
}),
handleSubmit: (values, form) => {
if (form.props.article != null) {
form.props.updateArticle({ ...form.props.article, ...values });
} else {
form.props.createArticle(values);
}
form.props.redirectToListing();
form.setSubmitting(false);
},
})
)(InnerForm);
================================================
FILE: src/features/articles/components/ArticleList.tsx
================================================
import { RootState } from 'MyTypes';
import React from 'react';
import { connect } from 'react-redux';
import * as selectors from '../selectors';
import ArticleListItem from './ArticleListItem';
const mapStateToProps = (state: RootState) => ({
isLoading: state.articles.isLoadingArticles,
articles: selectors.getArticles(state),
});
const dispatchProps = {};
type Props = ReturnType<typeof mapStateToProps> & typeof dispatchProps;
const ArticleList: React.FC<Props> = ({
isLoading,
articles: articles = [],
}) => {
if (isLoading) {
return <p style={{ textAlign: 'center' }}>Loading articles...</p>;
}
if (articles.length === 0) {
return (
<p style={{ textAlign: 'center' }}>
No articles yet, please create new...
</p>
);
}
return (
<ul style={getStyle()}>
{articles.map(article => (
<li key={article.id}>
<ArticleListItem article={article} />
</li>
))}
</ul>
);
};
const getStyle = (): React.CSSProperties => ({
textAlign: 'left',
margin: 'auto',
maxWidth: 500,
});
export default connect(
mapStateToProps,
dispatchProps
)(ArticleList);
================================================
FILE: src/features/articles/components/ArticleListItem.tsx
================================================
import { Article } from 'MyModels';
import React from 'react';
import areEqual from 'fast-deep-equal';
import { connect } from 'react-redux';
import { deleteArticleAsync } from '../actions';
import { getPath } from '../../../router-paths';
import FlexRow from '../../../components/FlexRow';
import { Link } from 'react-router-dom';
const dispatchProps = {
deleteArticle: deleteArticleAsync.request,
};
type Props = typeof dispatchProps & {
article: Article;
};
const ArticleListItem = React.memo<Props>(({ article, deleteArticle }) => {
return (
<FlexRow>
<div style={getStyle()}>{article.title}</div>
<FlexRow itemsSpacing={20}>
<Link to={getPath('viewArticle', article.id)}>View</Link>
<Link to={getPath('editArticle', article.id)}>Edit</Link>
<div
className="link"
onClick={() => deleteArticle(article)}
style={{ color: 'darkred' }}
>
Delete
</div>
</FlexRow>
</FlexRow>
);
}, areEqual);
const getStyle = (): React.CSSProperties => ({
overflowX: 'hidden',
textOverflow: 'ellipsis',
width: '300px',
});
export default connect(
null,
dispatchProps
)(ArticleListItem);
================================================
FILE: src/features/articles/components/ArticleView.tsx
================================================
import React from 'react';
import { Article } from 'MyModels';
type Props = {
article: Article;
};
const ArticleView: React.FC<Props> = ({ article }) => {
return (
<div>
<h3>{article.title}</h3>
<p>{article.content}</p>
</div>
);
};
export default ArticleView;
================================================
FILE: src/features/articles/epics.ts
================================================
import { RootEpic } from 'MyTypes';
import { from, of } from 'rxjs';
import { filter, switchMap, map, catchError } from 'rxjs/operators';
import { isActionOf } from 'typesafe-actions';
import {
loadArticlesAsync,
createArticleAsync,
updateArticleAsync,
deleteArticleAsync,
} from './actions';
export const loadArticlesEpic: RootEpic = (action$, state$, { api }) =>
action$.pipe(
filter(isActionOf(loadArticlesAsync.request)),
switchMap(() =>
from(api.articles.loadArticles()).pipe(
map(loadArticlesAsync.success),
catchError(message => of(loadArticlesAsync.failure(message)))
)
)
);
export const createArticlesEpic: RootEpic = (action$, state$, { api }) =>
action$.pipe(
filter(isActionOf(createArticleAsync.request)),
switchMap(action =>
from(api.articles.createArticle(action.payload)).pipe(
map(createArticleAsync.success),
catchError(message => of(createArticleAsync.failure(message)))
)
)
);
export const updateArticlesEpic: RootEpic = (action$, state$, { api }) =>
action$.pipe(
filter(isActionOf(updateArticleAsync.request)),
switchMap(action =>
from(api.articles.updateArticle(action.payload)).pipe(
map(updateArticleAsync.success),
catchError(message => of(updateArticleAsync.failure(message)))
)
)
);
export const deleteArticlesEpic: RootEpic = (action$, state$, { api, toast }) =>
action$.pipe(
filter(isActionOf(deleteArticleAsync.request)),
switchMap(action =>
from(api.articles.deleteArticle(action.payload)).pipe(
map(deleteArticleAsync.success),
catchError(message => {
toast.error(message);
return of(deleteArticleAsync.failure(action.payload));
})
)
)
);
================================================
FILE: src/features/articles/reducer.ts
================================================
import { Article } from 'MyModels';
import { combineReducers } from 'redux';
import { createReducer } from 'typesafe-actions';
import {
loadArticlesAsync,
createArticleAsync,
updateArticleAsync,
deleteArticleAsync,
} from './actions';
const reducer = combineReducers({
isLoadingArticles: createReducer(false as boolean)
.handleAction([loadArticlesAsync.request], (state, action) => true)
.handleAction(
[loadArticlesAsync.success, loadArticlesAsync.failure],
(state, action) => false
),
articles: createReducer([] as Article[])
.handleAction(
[
loadArticlesAsync.success,
createArticleAsync.success,
updateArticleAsync.success,
deleteArticleAsync.success,
],
(state, action) => action.payload
)
.handleAction(createArticleAsync.request, (state, action) => [
...state,
action.payload,
])
.handleAction(updateArticleAsync.request, (state, action) =>
state.map(i => (i.id === action.payload.id ? action.payload : i))
)
.handleAction(deleteArticleAsync.request, (state, action) =>
state.filter(i => i.id !== action.payload.id)
)
.handleAction(deleteArticleAsync.failure, (state, action) =>
state.concat(action.payload)
),
});
export default reducer;
================================================
FILE: src/features/articles/selectors.ts
================================================
import { RootState } from 'MyTypes';
// import { createSelector } from 'reselect';
export const getArticles = (state: RootState) => state.articles.articles;
================================================
FILE: src/features/articles/types.d.ts
================================================
declare module 'MyModels' {
export type Article = {
id: string;
title: string;
content: string;
};
}
================================================
FILE: src/index.css
================================================
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
a,
.link {
color: #61dafb;
cursor: pointer;
text-decoration: none;
}
a:hover,
.link:hover {
text-decoration: underline;
}
================================================
FILE: src/index.tsx
================================================
import '@babel/polyfill';
import 'tslib';
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import * as serviceWorker from './serviceWorker';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
================================================
FILE: src/layouts/Main.css
================================================
.App {
min-width: 500px;
}
.App-header {
background-color: #282c34;
min-height: 80px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
font-size: 24px;
color: white;
}
.App-main {
margin: 0 auto;
width: 500px;
overflow-x: hidden;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 40px;
pointer-events: none;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
================================================
FILE: src/layouts/Main.tsx
================================================
import React, { FC } from 'react';
import { Link } from 'react-router-dom';
import './Main.css';
import logo from '../assets/logo.svg';
import FlexRow from '../components/FlexRow';
type Props = {
renderActionsMenu?: () => JSX.Element;
};
const Main: FC<Props> = ({ children, renderActionsMenu }) => (
<div className="App">
<header className="App-header">
<FlexRow
grow={1}
align="center"
justify="space-between"
style={{ padding: '0 60px' }}
>
<FlexRow align="center">
<img src={logo} className="App-logo" alt="logo" />
<Link className="App-link" to="/">
Demo App
</Link>
</FlexRow>
{renderActionsMenu && renderActionsMenu()}
</FlexRow>
</header>
<main className="App-main">{children}</main>
</div>
);
export default Main;
================================================
FILE: src/react-app-env.d.ts
================================================
/// <reference types="react-scripts" />
================================================
FILE: src/router-paths.ts
================================================
const pathsMap = {
home: () => '/',
addArticle: () => '/add-article',
viewArticle: (articleId: string) => `/articles/${articleId}`,
editArticle: (articleId: string) => `/articles/${articleId}/edit`,
};
type PathsMap = typeof pathsMap;
export const getPath = <TRoute extends keyof PathsMap>(
route: TRoute,
...params: Parameters<PathsMap[TRoute]>
) => {
const pathCb: (...args: any[]) => string = pathsMap[route];
return pathCb(...params);
};
================================================
FILE: src/routes/AddArticle.tsx
================================================
import React from 'react';
import ArticleForm from '../features/articles/components/ArticleForm';
import Main from '../layouts/Main';
import BackLink from '../components/BackLink';
export default () => (
<Main renderActionsMenu={() => <BackLink />}>
<ArticleForm />
</Main>
);
================================================
FILE: src/routes/EditArticle.tsx
================================================
import { RootState } from 'MyTypes';
import React from 'react';
import { match } from 'react-router';
import ArticleForm from '../features/articles/components/ArticleForm';
import Main from '../layouts/Main';
import BackLink from '../components/BackLink';
import { connect } from 'react-redux';
type OwnProps = {
match: match<{ articleId: string }>;
};
const mapStateToProps = (state: RootState, ownProps: OwnProps) => ({
article: state.articles.articles.find(
i => i.id === ownProps.match.params.articleId
),
});
type Props = ReturnType<typeof mapStateToProps>;
const EditArticle = ({ article }: Props) => {
return (
<Main renderActionsMenu={() => <BackLink />}>
<ArticleForm article={article} />
</Main>
);
};
export default connect(mapStateToProps)(EditArticle);
================================================
FILE: src/routes/Home.tsx
================================================
import React from 'react';
import ArticleList from '../features/articles/components/ArticleList';
import ArticleActionsMenu from '../features/articles/components/ArticleActionsMenu';
import Main from '../layouts/Main';
export default () => (
<Main renderActionsMenu={() => <ArticleActionsMenu />}>
<ArticleList />
</Main>
);
================================================
FILE: src/routes/ViewArticle.tsx
================================================
import { RootState } from 'MyTypes';
import React from 'react';
import { connect } from 'react-redux';
import { match } from 'react-router';
import ArticleView from '../features/articles/components/ArticleView';
import Main from '../layouts/Main';
import BackLink from '../components/BackLink';
type OwnProps = {
match: match<{ articleId: string }>;
};
const mapStateToProps = (state: RootState, ownProps: OwnProps) => ({
article: state.articles.articles.find(
i => i.id === ownProps.match.params.articleId
),
});
type Props = ReturnType<typeof mapStateToProps>;
const ViewArticle = ({ article }: Props) => {
if (!article) {
return <div>'Article doesn\'t exist'</div>;
}
return (
<Main renderActionsMenu={() => <BackLink />}>
<ArticleView article={article} />
</Main>
);
};
export default connect(mapStateToProps)(ViewArticle);
================================================
FILE: src/serviceWorker.ts
================================================
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void;
onUpdate?: (registration: ServiceWorkerRegistration) => void;
};
export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(
(process as { env: { [key: string]: string } }).env.PUBLIC_URL,
window.location.href
);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
================================================
FILE: src/services/articles-api-client.ts
================================================
import { Article } from 'MyModels';
import * as localStorage from './local-storage-service';
let articles: Article[] = localStorage.get<Article[]>('articles') || [];
const TIMEOUT = 750;
export function loadArticles(): Promise<Article[]> {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(articles);
}, TIMEOUT);
});
}
export function createArticle(article: Article): Promise<Article[]> {
return new Promise((resolve, reject) => {
setTimeout(() => {
articles = articles.concat(article);
resolve(articles);
}, TIMEOUT);
});
}
export function updateArticle(article: Article): Promise<Article[]> {
return new Promise((resolve, reject) => {
setTimeout(() => {
articles = articles.map(i => (i.id === article.id ? article : i));
resolve(articles);
}, TIMEOUT);
});
}
export function deleteArticle(article: Article): Promise<Article[]> {
return new Promise((resolve, reject) => {
setTimeout(() => {
articles = articles.filter(i => i.id !== article.id);
resolve(articles);
}, TIMEOUT);
});
}
================================================
FILE: src/services/index.ts
================================================
import * as logger from './logger-service';
import * as articles from './articles-api-client';
import * as toast from './toast-service';
import * as localStorage from './local-storage-service';
export default {
logger,
localStorage,
toast,
api: {
articles,
},
};
================================================
FILE: src/services/local-storage-service.ts
================================================
const version = process.env.APP_VERSION || 0;
const PREFIX = `MY_APP_v${version}::`;
export function set<T = object>(key: string, value: T): void {
if (!localStorage) {
return;
}
try {
const serializedValue = JSON.stringify(value);
localStorage.setItem(PREFIX + key, serializedValue);
} catch (error) {
throw new Error('store serialization failed');
}
}
export function get<T = object>(key: string): T | undefined {
if (!localStorage) {
return;
}
try {
const serializedValue = localStorage.getItem(PREFIX + key);
if (serializedValue == null) {
return;
}
return JSON.parse(serializedValue);
} catch (error) {
throw new Error('store deserialization failed');
}
}
================================================
FILE: src/services/logger-service.ts
================================================
// TODO: connect external client logging service here (e.g. Sentry SDK)
// tslint:disable-next-line:no-console
export default { log: console.log };
================================================
FILE: src/services/toast-service.ts
================================================
import 'react-toastify/dist/ReactToastify.css';
import { toast } from 'react-toastify';
toast.configure();
const { info, warn, error, success } = toast;
export { info, warn, error, success };
================================================
FILE: src/services/types.d.ts
================================================
declare module 'MyTypes' {
export type Services = typeof import('./index').default;
}
================================================
FILE: src/store/index.ts
================================================
import { RootAction, RootState, Services } from 'MyTypes';
import { createStore, applyMiddleware } from 'redux';
import { createEpicMiddleware } from 'redux-observable';
import { createBrowserHistory } from 'history';
import { routerMiddleware } from 'connected-react-router';
import { composeEnhancers } from './utils';
import rootReducer from './root-reducer';
import rootEpic from './root-epic';
import services from '../services';
export const epicMiddleware = createEpicMiddleware<
RootAction,
RootAction,
RootState,
Services
>({
dependencies: services,
});
// configure middlewares
export const history = createBrowserHistory();
const middlewares = [routerMiddleware(history), epicMiddleware];
// compose enhancers
const enhancer = composeEnhancers(applyMiddleware(...middlewares));
// rehydrate state on app start
const initialState = {};
// create store
const store = createStore(rootReducer(history), initialState, enhancer);
epicMiddleware.run(rootEpic);
// export store singleton instance
export default store;
================================================
FILE: src/store/root-action.ts
================================================
import { routerActions } from 'connected-react-router';
import * as articlesActions from '../features/articles/actions';
export default {
router: routerActions,
articles: articlesActions,
};
================================================
FILE: src/store/root-epic.ts
================================================
import { combineEpics } from 'redux-observable';
import * as app from '../features/app/epics';
import * as articles from '../features/articles/epics';
export default combineEpics(...Object.values(app), ...Object.values(articles));
================================================
FILE: src/store/root-reducer.ts
================================================
import { combineReducers } from 'redux';
import { connectRouter } from 'connected-react-router';
import { History } from 'history';
import articles from '../features/articles/reducer';
const rootReducer = (history: History<any>) =>
combineReducers({
router: connectRouter(history),
articles,
});
export default rootReducer;
================================================
FILE: src/store/types.d.ts
================================================
import { StateType, ActionType } from 'typesafe-actions';
import { Epic } from 'redux-observable';
declare module 'MyTypes' {
export type Store = StateType<typeof import('./index').default>;
export type RootState = StateType<
ReturnType<typeof import('./root-reducer').default>
>;
export type RootAction = ActionType<typeof import('./root-action').default>;
export type RootEpic = Epic<RootAction, RootAction, RootState, Services>;
}
declare module 'typesafe-actions' {
interface Types {
RootAction: ActionType<typeof import('./root-action').default>;
}
}
================================================
FILE: src/store/utils.ts
================================================
import { compose } from 'redux';
export const composeEnhancers =
(process.env.NODE_ENV === 'development' &&
window &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||
compose;
================================================
FILE: tsconfig.json
================================================
{
"include": ["src", "typings", "lambdas/src"],
"exclude": ["src/**/*.spec.*"],
"extends": "./node_modules/react-redux-typescript-scripts/tsconfig.json",
"compilerOptions": {}
}
================================================
FILE: tsconfig.test.json
================================================
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs"
}
}
================================================
FILE: typings/augmentations.d.ts
================================================
export {};
// Fix incorrect ALBResult type
declare module 'aws-lambda' {
export interface ALBResult {
statusDescription?: string;
isBase64Encoded?: boolean;
}
}
================================================
FILE: typings/globals.d.ts
================================================
declare interface Window {
__REDUX_DEVTOOLS_EXTENSION__: any;
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__: any;
}
declare interface NodeModule {
hot?: { accept: (path: string, callback: () => void) => void };
}
declare interface System {
import<T = any>(module: string): Promise<T>;
}
declare var System: System;
================================================
FILE: typings/modules.d.ts
================================================
declare module '@emotion/styled/macro' {
import styled from '@emotion/styled';
export default styled;
}
gitextract_6ug6f53y/
├── .eslintrc
├── .gitignore
├── .prettierrc
├── .vscode/
│ └── settings.json
├── CODE_OF_CONDUCT.md
├── README.md
├── lambdas/
│ ├── .babelrc
│ ├── build/
│ │ └── hello.js
│ └── src/
│ └── hello.ts
├── netlify.toml
├── package.json
├── public/
│ ├── index.html
│ └── manifest.json
├── server/
│ ├── package.json
│ ├── src/
│ │ └── env.ts
│ └── tsconfig.json
├── src/
│ ├── App.test.tsx
│ ├── App.tsx
│ ├── components/
│ │ ├── BackLink.tsx
│ │ ├── FlexBox.tsx
│ │ ├── FlexColumn.tsx
│ │ └── FlexRow.tsx
│ ├── features/
│ │ ├── app/
│ │ │ └── epics.ts
│ │ └── articles/
│ │ ├── actions.ts
│ │ ├── components/
│ │ │ ├── ArticleActionsMenu.tsx
│ │ │ ├── ArticleForm.tsx
│ │ │ ├── ArticleList.tsx
│ │ │ ├── ArticleListItem.tsx
│ │ │ └── ArticleView.tsx
│ │ ├── epics.ts
│ │ ├── reducer.ts
│ │ ├── selectors.ts
│ │ └── types.d.ts
│ ├── index.css
│ ├── index.tsx
│ ├── layouts/
│ │ ├── Main.css
│ │ └── Main.tsx
│ ├── react-app-env.d.ts
│ ├── router-paths.ts
│ ├── routes/
│ │ ├── AddArticle.tsx
│ │ ├── EditArticle.tsx
│ │ ├── Home.tsx
│ │ └── ViewArticle.tsx
│ ├── serviceWorker.ts
│ ├── services/
│ │ ├── articles-api-client.ts
│ │ ├── index.ts
│ │ ├── local-storage-service.ts
│ │ ├── logger-service.ts
│ │ ├── toast-service.ts
│ │ └── types.d.ts
│ └── store/
│ ├── index.ts
│ ├── root-action.ts
│ ├── root-epic.ts
│ ├── root-reducer.ts
│ ├── types.d.ts
│ └── utils.ts
├── tsconfig.json
├── tsconfig.test.json
└── typings/
├── augmentations.d.ts
├── globals.d.ts
└── modules.d.ts
SYMBOL INDEX (64 symbols across 24 files)
FILE: lambdas/build/hello.js
function e (line 1) | function e(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{...
function e (line 1) | function e(t,r,e,n,o,i,a){try{var u=t[i](a),c=u.value}catch(t){return vo...
function u (line 1) | function u(t){e(a,o,i,u,c,"next",t)}
function c (line 1) | function c(t){e(a,o,i,u,c,"throw",t)}
function c (line 1) | function c(t,r,e,n){var o=r&&r.prototype instanceof d?r:d,i=Object.creat...
function f (line 1) | function f(t,r,e){try{return{type:"normal",arg:t.call(r,e)}}catch(t){ret...
function d (line 1) | function d(){}
function v (line 1) | function v(){}
function g (line 1) | function g(){}
function L (line 1) | function L(t){["next","throw","return"].forEach(function(r){t[r]=functio...
function E (line 1) | function E(t){var r;this._invoke=function(e,o){function i(){return new P...
function _ (line 1) | function _(t,e){var n=t.iterator[e.method];if(n===r){if(e.delegate=null,...
function O (line 1) | function O(t){var r={tryLoc:t[0]};1 in t&&(r.catchLoc=t[1]),2 in t&&(r.f...
function j (line 1) | function j(t){var r=t.completion||{};r.type="normal",delete r.arg,t.comp...
function P (line 1) | function P(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(O,this),this.r...
function S (line 1) | function S(t){if(t){var e=t[i];if(e)return e.call(t);if("function"==type...
function k (line 1) | function k(){return{value:r,done:!0}}
function o (line 1) | function o(n,o){return u.type="throw",u.arg=t,e.next=n,o&&(e.method="nex...
FILE: server/src/env.ts
constant DB_HOST (line 1) | const DB_HOST = process.env.DB_HOST;
FILE: src/App.tsx
class App (line 13) | class App extends Component {
method render (line 14) | render() {
FILE: src/components/BackLink.tsx
type Props (line 6) | interface Props {}
FILE: src/components/FlexBox.tsx
type Props (line 4) | type Props = {
FILE: src/components/FlexColumn.tsx
type Props (line 6) | type Props = React.ComponentProps<typeof FlexBox> & {
FILE: src/components/FlexRow.tsx
type Props (line 6) | type Props = React.ComponentProps<typeof FlexBox> & {
FILE: src/features/articles/components/ArticleActionsMenu.tsx
type Props (line 11) | type Props = ReturnType<typeof mapStateToProps> & typeof dispatchProps;
type State (line 13) | type State = {};
class ArticleActionsMenu (line 15) | class ArticleActionsMenu extends React.Component<Props, State> {
method render (line 16) | render() {
FILE: src/features/articles/components/ArticleForm.tsx
type FormValues (line 12) | type FormValues = Pick<Article, 'title' | 'content'> & {};
type Props (line 27) | type Props = typeof dispatchProps & {
FILE: src/features/articles/components/ArticleList.tsx
type Props (line 15) | type Props = ReturnType<typeof mapStateToProps> & typeof dispatchProps;
FILE: src/features/articles/components/ArticleListItem.tsx
type Props (line 15) | type Props = typeof dispatchProps & {
FILE: src/features/articles/components/ArticleView.tsx
type Props (line 5) | type Props = {
FILE: src/features/articles/types.d.ts
type Article (line 2) | type Article = {
FILE: src/layouts/Main.tsx
type Props (line 8) | type Props = {
FILE: src/router-paths.ts
type PathsMap (line 7) | type PathsMap = typeof pathsMap;
FILE: src/routes/EditArticle.tsx
type OwnProps (line 10) | type OwnProps = {
type Props (line 20) | type Props = ReturnType<typeof mapStateToProps>;
FILE: src/routes/ViewArticle.tsx
type OwnProps (line 10) | type OwnProps = {
type Props (line 20) | type Props = ReturnType<typeof mapStateToProps>;
FILE: src/serviceWorker.ts
type Config (line 23) | type Config = {
function register (line 28) | function register(config?: Config) {
function registerValidSW (line 65) | function registerValidSW(swUrl: string, config?: Config) {
function checkValidServiceWorker (line 109) | function checkValidServiceWorker(swUrl: string, config?: Config) {
function unregister (line 137) | function unregister() {
FILE: src/services/articles-api-client.ts
constant TIMEOUT (line 7) | const TIMEOUT = 750;
function loadArticles (line 9) | function loadArticles(): Promise<Article[]> {
function createArticle (line 17) | function createArticle(article: Article): Promise<Article[]> {
function updateArticle (line 26) | function updateArticle(article: Article): Promise<Article[]> {
function deleteArticle (line 35) | function deleteArticle(article: Article): Promise<Article[]> {
FILE: src/services/local-storage-service.ts
constant PREFIX (line 2) | const PREFIX = `MY_APP_v${version}::`;
function set (line 4) | function set<T = object>(key: string, value: T): void {
function get (line 17) | function get<T = object>(key: string): T | undefined {
FILE: src/services/types.d.ts
type Services (line 2) | type Services = typeof import('./index').default;
FILE: src/store/types.d.ts
type Store (line 5) | type Store = StateType<typeof import('./index').default>;
type RootState (line 6) | type RootState = StateType<
type RootAction (line 9) | type RootAction = ActionType<typeof import('./root-action').default>;
type RootEpic (line 11) | type RootEpic = Epic<RootAction, RootAction, RootState, Services>;
type Types (line 15) | interface Types {
FILE: typings/augmentations.d.ts
type ALBResult (line 5) | interface ALBResult {
FILE: typings/globals.d.ts
type Window (line 1) | interface Window {
type NodeModule (line 6) | interface NodeModule {
type System (line 10) | interface System {
Condensed preview — 61 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (56K chars).
[
{
"path": ".eslintrc",
"chars": 102,
"preview": "{\n \"extends\": [\n \"react-app\",\n \"./node_modules/react-redux-typescript-scripts/eslint.js\"\n ]\n}\n"
},
{
"path": ".gitignore",
"chars": 345,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/ser"
},
{
"path": ".prettierrc",
"chars": 88,
"preview": "{\n \"printWidth\": 80,\n \"semi\": true,\n \"singleQuote\": true,\n \"trailingComma\": \"es5\"\n}\n"
},
{
"path": ".vscode/settings.json",
"chars": 54,
"preview": "{\n \"typescript.tsdk\": \"node_modules/typescript/lib\"\n}"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 3355,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "README.md",
"chars": 1924,
"preview": "<div align=\"center\">\n\n# React, Redux, TypeScript - RealWorld App\n\n## 🚧🚧🚧 UNDER CONSTRUCTION 🚧🚧🚧\n\n### **LIVE DEMO: [LINK]"
},
{
"path": "lambdas/.babelrc",
"chars": 282,
"preview": "// lambda build config\n{\n \"presets\": [\"@babel/preset-typescript\", \"@babel/preset-env\"],\n \"plugins\": [\n \"@babel/tran"
},
{
"path": "lambdas/build/hello.js",
"chars": 8286,
"preview": "!function(t,r){for(var e in r)t[e]=r[e]}(exports,function(t){var r={};function e(n){if(r[n])return r[n].exports;var o=r["
},
{
"path": "lambdas/src/hello.ts",
"chars": 566,
"preview": "import { ALBHandler } from 'aws-lambda';\nimport querystring from 'querystring';\n\nexport const handler: ALBHandler = asyn"
},
{
"path": "netlify.toml",
"chars": 88,
"preview": "[build]\n functions = \"lambdas/build\" # netlify-lambda reads this for local dev server\n"
},
{
"path": "package.json",
"chars": 2774,
"preview": "{\n \"name\": \"react-redux-typescript-realworld-app\",\n \"description\": \"RealWorld App implementation based on \\\"react-redu"
},
{
"path": "public/index.html",
"chars": 1598,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <link rel=\"shortcut icon\" href=\"%PUBLIC_URL%/"
},
{
"path": "public/manifest.json",
"chars": 306,
"preview": "{\n \"short_name\": \"React App\",\n \"name\": \"Create React App Sample\",\n \"icons\": [\n {\n \"src\": \"favicon.ico\",\n "
},
{
"path": "server/package.json",
"chars": 453,
"preview": "{\n \"name\": \"server\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"description\": \"\",\n \"main\": \"index.js\",\n \"scripts\": {"
},
{
"path": "server/src/env.ts",
"chars": 44,
"preview": "export const DB_HOST = process.env.DB_HOST;\n"
},
{
"path": "server/tsconfig.json",
"chars": 171,
"preview": "{\n \"include\": [\"src\", \"typings\"],\n \"exclude\": [\"src/**/*.spec.*\"],\n \"extends\": \"./node_modules/react-redux-typescript"
},
{
"path": "src/App.test.tsx",
"chars": 248,
"preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport App from './App';\n\nit('renders without crashing', ()"
},
{
"path": "src/App.tsx",
"chars": 1245,
"preview": "import React, { Component } from 'react';\nimport { Provider } from 'react-redux';\nimport { ConnectedRouter } from 'conne"
},
{
"path": "src/components/BackLink.tsx",
"chars": 293,
"preview": "import React from 'react';\nimport areEqual from 'fast-deep-equal';\n\nimport { Link } from 'react-router-dom';\n\ninterface "
},
{
"path": "src/components/FlexBox.tsx",
"chars": 1034,
"preview": "import styled from '@emotion/styled/macro';\nimport { CSSObject } from '@emotion/core';\n\ntype Props = {\n className?: str"
},
{
"path": "src/components/FlexColumn.tsx",
"chars": 281,
"preview": "import React from 'react';\nimport { CSSObject } from '@emotion/core';\n\nimport FlexBox from './FlexBox';\n\ntype Props = Re"
},
{
"path": "src/components/FlexRow.tsx",
"chars": 278,
"preview": "import React from 'react';\nimport { CSSObject } from '@emotion/core';\n\nimport FlexBox from './FlexBox';\n\ntype Props = Re"
},
{
"path": "src/features/app/epics.ts",
"chars": 880,
"preview": "import { RootEpic } from 'MyTypes';\nimport { tap, ignoreElements, filter, first, map } from 'rxjs/operators';\n\nimport { "
},
{
"path": "src/features/articles/actions.ts",
"chars": 769,
"preview": "import { Article } from 'MyModels';\nimport { createAsyncAction } from 'typesafe-actions';\n\nexport const loadArticlesAsyn"
},
{
"path": "src/features/articles/components/ArticleActionsMenu.tsx",
"chars": 648,
"preview": "import { RootState } from 'MyTypes';\nimport React from 'react';\nimport { connect } from 'react-redux';\nimport { Link } f"
},
{
"path": "src/features/articles/components/ArticleForm.tsx",
"chars": 2254,
"preview": "import React from 'react';\nimport cuid from 'cuid';\nimport { Form, FormikProps, Field, withFormik, ErrorMessage } from '"
},
{
"path": "src/features/articles/components/ArticleList.tsx",
"chars": 1154,
"preview": "import { RootState } from 'MyTypes';\nimport React from 'react';\nimport { connect } from 'react-redux';\n\nimport * as sele"
},
{
"path": "src/features/articles/components/ArticleListItem.tsx",
"chars": 1199,
"preview": "import { Article } from 'MyModels';\nimport React from 'react';\nimport areEqual from 'fast-deep-equal';\nimport { connect "
},
{
"path": "src/features/articles/components/ArticleView.tsx",
"chars": 292,
"preview": "import React from 'react';\n\nimport { Article } from 'MyModels';\n\ntype Props = {\n article: Article;\n};\n\nconst ArticleVie"
},
{
"path": "src/features/articles/epics.ts",
"chars": 1790,
"preview": "import { RootEpic } from 'MyTypes';\nimport { from, of } from 'rxjs';\nimport { filter, switchMap, map, catchError } from "
},
{
"path": "src/features/articles/reducer.ts",
"chars": 1305,
"preview": "import { Article } from 'MyModels';\nimport { combineReducers } from 'redux';\nimport { createReducer } from 'typesafe-act"
},
{
"path": "src/features/articles/selectors.ts",
"chars": 158,
"preview": "import { RootState } from 'MyTypes';\n// import { createSelector } from 'reselect';\n\nexport const getArticles = (state: R"
},
{
"path": "src/features/articles/types.d.ts",
"chars": 117,
"preview": "declare module 'MyModels' {\n export type Article = {\n id: string;\n title: string;\n content: string;\n };\n}\n"
},
{
"path": "src/index.css",
"chars": 512,
"preview": "body {\n margin: 0;\n padding: 0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n "
},
{
"path": "src/index.tsx",
"chars": 495,
"preview": "import '@babel/polyfill';\nimport 'tslib';\nimport React from 'react';\nimport ReactDOM from 'react-dom';\n\nimport './index."
},
{
"path": "src/layouts/Main.css",
"chars": 508,
"preview": ".App {\n min-width: 500px;\n}\n\n.App-header {\n background-color: #282c34;\n min-height: 80px;\n display: flex;\n flex-dir"
},
{
"path": "src/layouts/Main.tsx",
"chars": 861,
"preview": "import React, { FC } from 'react';\nimport { Link } from 'react-router-dom';\n\nimport './Main.css';\nimport logo from '../a"
},
{
"path": "src/react-app-env.d.ts",
"chars": 40,
"preview": "/// <reference types=\"react-scripts\" />\n"
},
{
"path": "src/router-paths.ts",
"chars": 460,
"preview": "const pathsMap = {\n home: () => '/',\n addArticle: () => '/add-article',\n viewArticle: (articleId: string) => `/articl"
},
{
"path": "src/routes/AddArticle.tsx",
"chars": 287,
"preview": "import React from 'react';\n\nimport ArticleForm from '../features/articles/components/ArticleForm';\nimport Main from '../"
},
{
"path": "src/routes/EditArticle.tsx",
"chars": 800,
"preview": "import { RootState } from 'MyTypes';\nimport React from 'react';\nimport { match } from 'react-router';\n\nimport ArticleFor"
},
{
"path": "src/routes/Home.tsx",
"chars": 335,
"preview": "import React from 'react';\n\nimport ArticleList from '../features/articles/components/ArticleList';\nimport ArticleActions"
},
{
"path": "src/routes/ViewArticle.tsx",
"chars": 871,
"preview": "import { RootState } from 'MyTypes';\nimport React from 'react';\nimport { connect } from 'react-redux';\nimport { match } "
},
{
"path": "src/serviceWorker.ts",
"chars": 5201,
"preview": "// This optional code is used to register a service worker.\n// register() is not called by default.\n\n// This lets the ap"
},
{
"path": "src/services/articles-api-client.ts",
"chars": 1101,
"preview": "import { Article } from 'MyModels';\n\nimport * as localStorage from './local-storage-service';\n\nlet articles: Article[] ="
},
{
"path": "src/services/index.ts",
"chars": 278,
"preview": "import * as logger from './logger-service';\nimport * as articles from './articles-api-client';\nimport * as toast from '."
},
{
"path": "src/services/local-storage-service.ts",
"chars": 731,
"preview": "const version = process.env.APP_VERSION || 0;\nconst PREFIX = `MY_APP_v${version}::`;\n\nexport function set<T = object>(ke"
},
{
"path": "src/services/logger-service.ts",
"chars": 148,
"preview": "// TODO: connect external client logging service here (e.g. Sentry SDK)\n// tslint:disable-next-line:no-console\nexport de"
},
{
"path": "src/services/toast-service.ts",
"chars": 194,
"preview": "import 'react-toastify/dist/ReactToastify.css';\nimport { toast } from 'react-toastify';\ntoast.configure();\n\nconst { info"
},
{
"path": "src/services/types.d.ts",
"chars": 88,
"preview": "declare module 'MyTypes' {\n export type Services = typeof import('./index').default;\n}\n"
},
{
"path": "src/store/index.ts",
"chars": 1039,
"preview": "import { RootAction, RootState, Services } from 'MyTypes';\nimport { createStore, applyMiddleware } from 'redux';\nimport "
},
{
"path": "src/store/root-action.ts",
"chars": 196,
"preview": "import { routerActions } from 'connected-react-router';\nimport * as articlesActions from '../features/articles/actions';"
},
{
"path": "src/store/root-epic.ts",
"chars": 233,
"preview": "import { combineEpics } from 'redux-observable';\n\nimport * as app from '../features/app/epics';\nimport * as articles fro"
},
{
"path": "src/store/root-reducer.ts",
"chars": 339,
"preview": "import { combineReducers } from 'redux';\nimport { connectRouter } from 'connected-react-router';\nimport { History } from"
},
{
"path": "src/store/types.d.ts",
"chars": 581,
"preview": "import { StateType, ActionType } from 'typesafe-actions';\nimport { Epic } from 'redux-observable';\n\ndeclare module 'MyTy"
},
{
"path": "src/store/utils.ts",
"chars": 188,
"preview": "import { compose } from 'redux';\n\nexport const composeEnhancers =\n (process.env.NODE_ENV === 'development' &&\n windo"
},
{
"path": "tsconfig.json",
"chars": 186,
"preview": "{\n \"include\": [\"src\", \"typings\", \"lambdas/src\"],\n \"exclude\": [\"src/**/*.spec.*\"],\n \"extends\": \"./node_modules/react-r"
},
{
"path": "tsconfig.test.json",
"chars": 87,
"preview": "{\n \"extends\": \"./tsconfig.json\",\n \"compilerOptions\": {\n \"module\": \"commonjs\"\n }\n}"
},
{
"path": "typings/augmentations.d.ts",
"chars": 174,
"preview": "export {};\n\n// Fix incorrect ALBResult type\ndeclare module 'aws-lambda' {\n export interface ALBResult {\n statusDescr"
},
{
"path": "typings/globals.d.ts",
"chars": 316,
"preview": "declare interface Window {\n __REDUX_DEVTOOLS_EXTENSION__: any;\n __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: any;\n}\n\ndeclare "
},
{
"path": "typings/modules.d.ts",
"chars": 108,
"preview": "declare module '@emotion/styled/macro' {\n import styled from '@emotion/styled';\n export default styled;\n}\n"
}
]
About this extraction
This page contains the full source code of the piotrwitek/react-redux-typescript-realworld-app GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 61 files (49.5 KB), approximately 14.4k tokens, and a symbol index with 64 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.