diff --git a/App-Description.md b/App-Description.md new file mode 100644 index 0000000..6c8abc4 --- /dev/null +++ b/App-Description.md @@ -0,0 +1,64 @@ +# Day 19 - Application Architecture and Endpoint Summary + +### ๐Ÿ”ง Overview + +This application demonstrates a full-stack setup using **React + Redux** on the frontend and **Express + MongoDB** on the backend. It is structured to showcase multiple features while maintaining clarity for learning purposes. The frontend handles the UI and state management using Redux, while the backend manages API routes, database operations, and server-side logic. + +### ๐Ÿ“ Project Setup + +This Project is cloned from [Repo](https://github.com/PiyushB752/express-react-fullstack_Zolvit_Day-19). The dependencies are installed using the command "npm install" or "npm i". This project can start using the command "npm run dev". + +### โš™๏ธ Technology Stack + +| Layer | Technology | +|-------------|----------------------------------| +| Frontend | React, Redux, Redux-Saga | +| Backend | Node.js, Express.js | +| Database | MongoDB (via Mongoose) | +| Dev Tools | Babel, dotenv, Webpack, Concurrently | + +### ๐Ÿ—‚ App Structure + +#### ๐Ÿ–ฅ๏ธ Frontend + +The frontend of this application is built using **React + Redux**. Key components include: + +**UI Components** +1. **Dashboard.jsx** โ€“ Main dashboard interface +2. **Login.jsx** and **Signup.jsx** โ€“ Authentication screens +3. **Navigation.jsx** โ€“ Navigation bar +4. **TaskDetails.jsx** and **TaskList.jsx** โ€“ Task-related views + +**Redux / State Management** +1. **mutations.js** โ€“ Contains Redux action creators +2. **sagas.js** โ€“ Handles side effects using Redux-Saga +3. **sagas.mock.js** โ€“ Mock data for sagas + +#### ๐ŸŒ Backend + +The backend of this application is built using **Express.js + MongoDB**. Key files include: + +1. **server.js** โ€“ Main entry point for the Express server +2. **authenticate.js** โ€“ Handles user authentication logic +3. **connect-db.js** โ€“ Establishes connection to MongoDB +4. **initialize-db.js** โ€“ Seeds the database with initial data +5. **communicate-db.js** โ€“ Contains functions to query and mutate the database + +### ๐Ÿž๏ธ Environment Configuration + +| Setting | Value | +|----------------|-----------------------------------------| +| MongoDB URI | `mongodb://localhost:27017/organizer` | +| Frontend Port | `8080` | +| Backend Port | `7777` | + +### ๐Ÿ“ก API Endpoints + +| Method | Endpoint | Description | +|--------|------------------|-----------------------------------------| +| POST | `/task/new` | Adds a new task to the database. | +| POST | `/task/update` | Updates an existing task in the database. | +| POST | `/comment/new` | Adds a new comment to the task. | +| POST | `/user/create` | Creating new users | +| POST | `/authenticate` | User authentication | +| GET | `/tasks` | Getting all task data | \ No newline at end of file diff --git a/MongoMemoryValidations.md b/MongoMemoryValidations.md new file mode 100644 index 0000000..2620fa5 --- /dev/null +++ b/MongoMemoryValidations.md @@ -0,0 +1,50 @@ +# API Integration Test Validations + +This document describes the validations performed by the integration tests using an in-memory MongoDB (`mongodb-memory-server`) for isolated testing. + + +## โš™ Environment + +- **Database:** In-memory MongoDB (`MongoMemoryServer`) +- **App:** Express API tested using `Mocha` + `chai` + `Supertest`. + + +## โœ… Validations Tested + +### 1๏ธโƒฃ User Creation + +- **Endpoint:** `POST /user/create` +- **Validation Logic:** + - A new user can be created successfully. + - Response contains: + - `userID` + - `state.session.authenticated === 'AUTHENTICATED'` + +### 2๏ธโƒฃ User Authentication + +- **Endpoint:** `POST /authenticate` +- **Validation Logic:** + - **Valid credentials:** + - Returns a `token`. + - `state.session.authenticated === 'AUTHENTICATED'`. + - **Invalid password:** + - Returns HTTP `500`. + - Response text is `'Password incorrect'`. + +### 3๏ธโƒฃ Task Creation + +- **Endpoint:** `POST /task/new` +- **Validation Logic:** + - A new task can be added with: + - `name` (string) + - `isComplete` (boolean) + - `owner` (user ID string) + +### 4๏ธโƒฃ Task Update + +- **Endpoint:** `POST /task/update` +- **Validation Logic:** + - A task can be updated with: + - `id` (task ID) + - New `name` + - Updated `isComplete` status \ No newline at end of file diff --git a/Performance_Report.md b/Performance_Report.md new file mode 100644 index 0000000..6a27d94 --- /dev/null +++ b/Performance_Report.md @@ -0,0 +1,76 @@ +# ๐Ÿ“Š k6 Load Testing Report + +This report documents the load testing of the `http://localhost:7777` endpoints using [k6](https://k6.io). The goal is to analyze **performance**, **reliability**, and **correctness** under simulated user load. + +## ๐Ÿ“… Test Environment & Setup + +| Aspect | Details | +|---------------------|-------------------------------------| +| API Endpoint | http://localhost:7777 (multiple APIs) | +| Test Tool | k6 Load Testing Tool | +| Test Duration | 40 seconds | +| Virtual Users | Ramp-up to 100 concurrent users | +| Request Type | HTTP POST (various endpoints) | +| Network Conditions | Localhost (minimal network latency) | + +## ๐Ÿงช Test Objective + +- Simulate up to 100 virtual users (VUs) hitting various API endpoints +- Measure API response time and throughput under load +- Validate API correctness using k6 checks +- Identify any errors, failures, or timeouts + +## โœ๏ธ Final Output + +``` +โœ“ /user/create status 200 or 500 +โœ“ /authenticate status 200 +โœ“ /authenticate has token +โœ“ /task/new status 200 +โœ“ /task/update status 200 +โœ“ /comment/new status 200 + +HTTP +http_req_duration.......................................................: avg=56.55ms min=471.6ยตs med=36.97ms max=393.13ms p(90)=177.04ms p(95)=259.53ms + { expected_response:true }............................................: avg=61.83ms min=471.6ยตs med=37.5ms max=393.13ms p(90)=214.22ms p(95)=262.59ms +http_req_failed.........................................................: 17.56% 7219 out of 41095 +http_reqs...............................................................: 41095 1026.737476/s + +EXECUTION +iteration_duration......................................................: avg=283.4ms min=12.05ms med=300.09ms max=800.75ms p(90)=453.01ms p(95)=468.45ms +iterations..............................................................: 8219 205.347495/s +vus.....................................................................: 1 min=1 max=100 +vus_max................................................................: 100 min=100 max=100 + +NETWORK +data_received...........................................................: 14 MB 344 kB/s +data_sent...............................................................: 7.3 MB 181 kB/s +``` + +## ๐Ÿ” Detailed Metrics Explanation + +| Metric | Explanation | Interpretation | +|---------------------------|----------------------------------------------------------------------|----------------------------------------------------------------------| +| **http_req_duration (avg)** | Average duration of HTTP requests. | Requests were fast: ~56.55 ms on average. | +| **http_req_duration (p90)** | 90% of requests completed faster than this time. | 90% of requests < 177 ms โ€” good latency. | +| **http_req_duration (p95)** | 95% of requests completed faster than this time. | 95% of requests < 259 ms โ€” minor spikes in latency. | +| **http_req_failed** | % of requests that failed (non-2xx or network errors). | 17.56% failed โ€” needs investigation (likely /user/create 500 errors).| +| **http_reqs** | Total HTTP requests sent. | 41,095 requests made during test. | +| **iteration_duration (avg)**| Avg duration of a full VU iteration. | Each test loop took ~283 ms. | +| **iteration_duration (p90)**| 90% of loops finished in < this time. | 453 ms per loop at 90th percentile โ€” smooth loop timing. | +| **iteration_duration (p95)**| 95% of loops finished in < this time. | 468 ms shows slight delays under high load. | +| **vus** | Active virtual users at a time. | Reached target 100 VUs. | +| **vus_max** | Max number of VUs during test. | Successfully scaled to 100 VUs. | +| **data_received** | Total data received from server. | 14 MB, expected for small API responses. | +| **data_sent** | Total data sent to server. | 7.3 MB of POST request bodies. | + +## ๐Ÿ“ˆ Interpretation Summary + +The load test results indicate: + +- **Latency:** Response times were excellent overall, with most requests under 200 ms even under high load. +- **Reliability:** 17.56% of requests failed โ€” likely due to duplicate user creation or auth conflicts. Needs error handling improvements. +- **Throughput:** Achieved over 1,000 requests/sec locally โ€” strong performance. +- **Correctness:** All checks passed โ€” status codes, tokens, empty bodies where expected. +- **User Simulation:** Full 100 VUs were active; concurrency target achieved. +- **Data Volume:** Reasonable amounts of data sent/received, typical for API CRUD operations. diff --git a/README.md b/README.md index 3320941..0e6a7ae 100644 --- a/README.md +++ b/README.md @@ -69,3 +69,6 @@ This version of the application is found at the [Add Sign Up Branch](https://git ### Security Coming February 2019. + +## Walkthrought Video +link - https://drive.google.com/file/d/1nLH_jtvvDCj5r4yV3LSVHrjZI1ZI6Jmh/view?usp=sharing diff --git a/Test_Plan.md b/Test_Plan.md new file mode 100644 index 0000000..f7b1560 --- /dev/null +++ b/Test_Plan.md @@ -0,0 +1,107 @@ +# Test Plan + +This document outlines the test strategy, tools, and execution flow for the `express-react-fullstack` application. + +## ๐Ÿ“Œ **Test Types & Coverage** + +### 1๏ธโƒฃ **Unit Tests** +- **Scope:** Test isolated utility functions, specifically `assembleUserState` logic. +- **Tooling:** `mocha`, `chai` +- **Location:** `test/BackendTestingFolder/unit.test.js` +- **Focus:** + - User session state assembly. + - Correct retrieval of user tasks, groups, users, and comments. + +- **Run Command:** + ```bash + npm run test-backend + ``` + +- **Success Criteria:** + - All utility functions produce correct output. + - Data integrity in user state assembly is verified. + +### 2๏ธโƒฃ **Integration Tests** +- **Scope:** Verify API routes and DB interactions. +- **Tooling:** `mocha`, `chai`, `supertest` +- **Location:** + - `test/BackendTestingFolder/integrate.test.js` + - `test/BackendTestingFolder/mongoMemory.test.js` +- **Focus:** + - Create and authenticate users. + - Create, update tasks. + - DB consistency between real MongoDB and in-memory DB. + +- **Run Command:** + ```bash + npm run test-backend + ``` + +- **Success Criteria:** + - API endpoints return correct HTTP status and response payloads. + - Database reflects expected changes. + +### 3๏ธโƒฃ **Frontend Selenium Tests** +- **Scope:** Browser automation simulating user actions. +- **Tooling:** `selenium-webdriver`, `mocha`, `chai` +- **Location:** `test/FrontendTestingFolder/selenium.test.mjs` +- **Focus:** + - Sign up new user. + - Login user. + - Verify redirection to dashboard. + - Capture screenshots during each step. + +- **Run Command:** + ```bash + npm run test-frontend + ``` + +- **Success Criteria:** + - Navigation flow succeeds without errors. + - Screenshots captured at key milestones. + - URLs match expected paths. + +### 4๏ธโƒฃ **Load Testing** +- **Scope:** Validate system performance under load. +- **Tooling:** `k6` +- **Location:** `test/PerformanceTestingFolder/load_test.js` +- **Focus:** + - Load test user creation, authentication, task/comment operations. + - Assess response codes and basic response validation. + +- **Run Command:** + ```bash + k6 run test/PerformanceTestingFolder/load_test.js + ``` + +- **Success Criteria:** + - APIs handle increasing load without failure. + - Response times stay within acceptable limits. + +## โš™ **Test Execution Flow** + +```bash +1๏ธโƒฃ Run backend unit and integration tests - npm run test-backend + +2๏ธโƒฃ Run frontend Selenium E2E tests - npm run test-frontend + +3๏ธโƒฃ Run load tests - k6 run test/PerformanceTestingFolder/load_test.js +``` + +## ๐ŸŒ **Environment Notes** +- **Real DB Integration:** Run `npm run server` +- **Memory DB Integration:** Run `npm run server-memory` +- **Dev mode (for Selenium):** Run `npm run start-dev` + +## ๐Ÿ“‚ **File Locations** +| Test Type | File Path | +|----------------|-----------------------------------------------| +| Unit | `test/BackendTestingFolder/unit.test.js` | +| Integration | `test/BackendTestingFolder/integrate.test.js` | +| Integration (Memory) | `test/BackendTestingFolder/mongoMemory.test.js` | +| Frontend E2E | `test/FrontendTestingFolder/selenium.test.mjs` | +| Load Test | `test/PerformanceTestingFolder/load_test.js` | + +## โœ… **Summary** + +This test plan ensures the correctness, resilience, and performance of `express-react-fullstack` through comprehensive unit, integration, E2E, and load tests using a modern JS testing stack. \ No newline at end of file diff --git a/dist/bundle.js b/dist/bundle.js index bde7acf..08473f8 100644 --- a/dist/bundle.js +++ b/dist/bundle.js @@ -1,37 +1,45 @@ -!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=58)}([function(e,t,n){"use strict";e.exports=n(26)},function(e,t,n){e.exports=n(33)()},function(e,t,n){"use strict";e.exports=function(){}},function(e,t,n){"use strict";e.exports=function(e,t,n,r,o,i,a,u){if(!e){var l;if(void 0===t)l=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var c=[n,r,o,i,a,u],s=0;(l=new Error(t.replace(/%s/g,function(){return c[s++]}))).name="Invariant Violation"}throw l.framesToPop=1,l}}},function(e,t,n){"use strict";var r=n(16),o=n(39),i=Object.prototype.toString;function a(e){return"[object Array]"===i.call(e)}function u(e){return null!==e&&"object"==typeof e}function l(e){return"[object Function]"===i.call(e)}function c(e,t){if(null!==e&&void 0!==e)if("object"!=typeof e&&(e=[e]),a(e))for(var n=0,r=e.length;n=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*"}}};r.forEach(["delete","get","head"],function(e){u.headers[e]={}}),r.forEach(["post","put","patch"],function(e){u.headers[e]=r.merge(i)}),e.exports=u}).call(this,n(41))},function(e,t,n){"use strict";(function(e,r){var o,i=n(24);o="undefined"!=typeof self?self:"undefined"!=typeof window?window:void 0!==e?e:r;var a=Object(i.a)(o);t.a=a}).call(this,n(10),n(35)(e))},function(e,t,n){"use strict"; +!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=51)}([function(e,t,n){"use strict";e.exports=n(22)},function(e,t,n){e.exports=n(26)()},function(e,t,n){"use strict";e.exports=function(e,t,n,r,o,i,a,u){if(!e){var l;if(void 0===t)l=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var c=[n,r,o,i,a,u],s=0;(l=new Error(t.replace(/%s/g,(function(){return c[s++]})))).name="Invariant Violation"}throw l.framesToPop=1,l}}},function(e,t,n){"use strict";var r=n(14),o=n(33),i=Object.prototype.toString;function a(e){return"[object Array]"===i.call(e)}function u(e){return null!==e&&"object"==typeof e}function l(e){return"[object Function]"===i.call(e)}function c(e,t){if(null!=e)if("object"!=typeof e&&(e=[e]),a(e))for(var n=0,r=e.length;n-1?"[^"+c(e)+"]+?":c(t)+"|(?:(?!"+c(t)+")[^"+c(e)+"])+?"}function u(e){return encodeURI(e).replace(/[\/?#]/g,(function(e){return"%"+e.charCodeAt(0).toString(16).toUpperCase()}))}function l(e,t){for(var n=new Array(e.length),o=0;o=200&&e<300}};l.headers={common:{Accept:"application/json, text/plain, */*"}},r.forEach(["delete","get","head"],(function(e){l.headers[e]={}})),r.forEach(["post","put","patch"],(function(e){l.headers[e]=r.merge(i)})),e.exports=l}).call(this,n(35))},function(e,t,n){"use strict";e.exports=n(28)},function(e,t,n){"use strict"; /* object-assign (c) Sindre Sorhus @license MIT -*/var r=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable;e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map(function(e){return t[e]}).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach(function(e){r[e]=e}),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}()?Object.assign:function(e,t){for(var n,a,u=function(e){if(null===e||void 0===e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}(e),l=1;l>>((3&t)<<3)&255;return o}}},function(e,t){for(var n=[],r=0;r<256;++r)n[r]=(r+256).toString(16).substr(1);e.exports=function(e,t){var r=t||0,o=n;return[o[e[r++]],o[e[r++]],o[e[r++]],o[e[r++]],"-",o[e[r++]],o[e[r++]],"-",o[e[r++]],o[e[r++]],"-",o[e[r++]],o[e[r++]],"-",o[e[r++]],o[e[r++]],o[e[r++]],o[e[r++]],o[e[r++]],o[e[r++]]].join("")}},function(e,t,n){"use strict";e.exports=function(e,t){return function(){for(var n=new Array(arguments.length),r=0;r=t.length?n(new u(h,w,new a(void 0,e[w]))):s(e[w],t[w],n,r,h,w,d);for(;w=0?(s(e[o],t[o],n,r,h,o,d),k=l(k,a)):s(e[o],void 0,n,r,h,o,d)}),k.forEach(function(e){s(void 0,t[e],n,r,h,e,d)})}d.length=d.length-1}else e!==t&&("number"===y&&isNaN(e)&&isNaN(t)||n(new o(h,e,t)))}function f(e,t,n,r){return r=r||[],s(e,t,function(e){e&&r.push(e)},n),r.length?r:void 0}function p(e,t,n){if(e&&t&&n&&n.kind){for(var r=e,o=-1,i=n.path?n.path.length-1:0;++o0&&void 0!==arguments[0]?arguments[0]:{},t=Object.assign({},C,e),n=t.logger,r=t.stateTransformer,o=t.errorTransformer,i=t.predicate,a=t.logErrors,u=t.diffPredicate;if(void 0===n)return function(){return function(e){return function(t){return e(t)}}};if(e.getState&&e.dispatch)return console.error("[redux-logger] redux-logger not installed. Make sure to pass logger instance as middleware:\n// Logger with default options\nimport { logger } from 'redux-logger'\nconst store = createStore(\n reducer,\n applyMiddleware(logger)\n)\n// Or you can create your own logger with custom options http://bit.ly/redux-logger-options\nimport createLogger from 'redux-logger'\nconst logger = createLogger({\n // ...options\n});\nconst store = createStore(\n reducer,\n applyMiddleware(logger)\n)\n"),function(){return function(e){return function(t){return e(t)}}};var l=[];return function(e){var n=e.getState;return function(e){return function(c){if("function"==typeof i&&!i(n,c))return e(c);var s={};l.push(s),s.started=E.now(),s.startedTime=new Date,s.prevState=r(n()),s.action=c;var f=void 0;if(a)try{f=e(c)}catch(e){s.error=o(e)}else f=e(c);s.took=E.now()-s.started,s.nextState=r(n());var p=t.diff&&"function"==typeof u?u(n,c):t.diff;if(m(l,Object.assign({},t,{diff:p})),l.length=0,s.error)throw s.error;return f}}}}var v,g,b=function(e,t){return function(e,t){return new Array(t+1).join(e)}("0",t-e.toString().length)+e},w=function(e){return b(e.getHours(),2)+":"+b(e.getMinutes(),2)+":"+b(e.getSeconds(),2)+"."+b(e.getMilliseconds(),3)},E="undefined"!=typeof performance&&null!==performance&&"function"==typeof performance.now?performance:Date,x="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},k=function(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:{},t=e.dispatch,n=e.getState;return"function"==typeof t||"function"==typeof n?y()({dispatch:t,getState:n}):void console.error("\n[redux-logger v3] BREAKING CHANGE\n[redux-logger v3] Since 3.0.0 redux-logger exports by default logger with default settings.\n[redux-logger v3] Change\n[redux-logger v3] import createLogger from 'redux-logger'\n[redux-logger v3] to\n[redux-logger v3] import { createLogger } from 'redux-logger'\n")};t.defaults=C,t.createLogger=y,t.logger=_,t.default=_,Object.defineProperty(t,"__esModule",{value:!0})}(t)}).call(this,n(10))},function(e,t,n){"use strict"; -/** @license React v16.4.2 +*/var r=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable;function a(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}()?Object.assign:function(e,t){for(var n,u,l=a(e),c=1;c>>((3&t)<<3)&255;return o}}},function(e,t){for(var n=[],r=0;r<256;++r)n[r]=(r+256).toString(16).substr(1);e.exports=function(e,t){var r=t||0,o=n;return[o[e[r++]],o[e[r++]],o[e[r++]],o[e[r++]],"-",o[e[r++]],o[e[r++]],"-",o[e[r++]],o[e[r++]],"-",o[e[r++]],o[e[r++]],"-",o[e[r++]],o[e[r++]],o[e[r++]],o[e[r++]],o[e[r++]],o[e[r++]]].join("")}},function(e,t,n){"use strict";e.exports=function(e,t){return function(){for(var n=new Array(arguments.length),r=0;r=t.length?n(new u(h,w,new a(void 0,e[w]))):s(e[w],t[w],n,r,h,w,d);for(;w=0?(s(e[o],t[o],n,r,h,o,d),x=l(x,a)):s(e[o],void 0,n,r,h,o,d)})),x.forEach((function(e){s(void 0,t[e],n,r,h,e,d)}))}d.length=d.length-1}else e!==t&&("number"===v&&isNaN(e)&&isNaN(t)||n(new o(h,e,t)))}function f(e,t,n,r){return r=r||[],s(e,t,(function(e){e&&r.push(e)}),n),r.length?r:void 0}function p(e,t,n){if(e&&t&&n&&n.kind){for(var r=e,o=-1,i=n.path?n.path.length-1:0;++o0&&void 0!==arguments[0]?arguments[0]:{},t=Object.assign({},P,e),n=t.logger,r=t.stateTransformer,o=t.errorTransformer,i=t.predicate,a=t.logErrors,u=t.diffPredicate;if(void 0===n)return function(){return function(e){return function(t){return e(t)}}};if(e.getState&&e.dispatch)return console.error("[redux-logger] redux-logger not installed. Make sure to pass logger instance as middleware:\n// Logger with default options\nimport { logger } from 'redux-logger'\nconst store = createStore(\n reducer,\n applyMiddleware(logger)\n)\n// Or you can create your own logger with custom options http://bit.ly/redux-logger-options\nimport createLogger from 'redux-logger'\nconst logger = createLogger({\n // ...options\n});\nconst store = createStore(\n reducer,\n applyMiddleware(logger)\n)\n"),function(){return function(e){return function(t){return e(t)}}};var l=[];return function(e){var n=e.getState;return function(e){return function(c){if("function"==typeof i&&!i(n,c))return e(c);var s={};l.push(s),s.started=x.now(),s.startedTime=new Date,s.prevState=r(n()),s.action=c;var f=void 0;if(a)try{f=e(c)}catch(e){s.error=o(e)}else f=e(c);s.took=x.now()-s.started,s.nextState=r(n());var p=t.diff&&"function"==typeof u?u(n,c):t.diff;if(v(l,Object.assign({},t,{diff:p})),l.length=0,s.error)throw s.error;return f}}}}var g,b,w=function(e,t){return function(e,t){return new Array(t+1).join(e)}("0",t-e.toString().length)+e},E=function(e){return w(e.getHours(),2)+":"+w(e.getMinutes(),2)+":"+w(e.getSeconds(),2)+"."+w(e.getMilliseconds(),3)},x="undefined"!=typeof performance&&null!==performance&&"function"==typeof performance.now?performance:Date,T="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},k=function(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:{},t=e.dispatch,n=e.getState;return"function"==typeof t||"function"==typeof n?y()({dispatch:t,getState:n}):void console.error("\n[redux-logger v3] BREAKING CHANGE\n[redux-logger v3] Since 3.0.0 redux-logger exports by default logger with default settings.\n[redux-logger v3] Change\n[redux-logger v3] import createLogger from 'redux-logger'\n[redux-logger v3] to\n[redux-logger v3] import { createLogger } from 'redux-logger'\n")};t.defaults=P,t.createLogger=y,t.logger=_,t.default=_,Object.defineProperty(t,"__esModule",{value:!0})}(t)}).call(this,n(31))},function(e,t,n){"use strict"; +/** @license React v16.14.0 * react.production.min.js * - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var r=n(13),o=n(27),i=n(28),a=n(29),u="function"==typeof Symbol&&Symbol.for,l=u?Symbol.for("react.element"):60103,c=u?Symbol.for("react.portal"):60106,s=u?Symbol.for("react.fragment"):60107,f=u?Symbol.for("react.strict_mode"):60108,p=u?Symbol.for("react.profiler"):60114,d=u?Symbol.for("react.provider"):60109,h=u?Symbol.for("react.context"):60110,m=u?Symbol.for("react.async_mode"):60111,y=u?Symbol.for("react.forward_ref"):60112;u&&Symbol.for("react.timeout");var v="function"==typeof Symbol&&Symbol.iterator;function g(e){for(var t=arguments.length-1,n="https://reactjs.org/docs/error-decoder.html?invariant="+e,r=0;rN.length&&N.push(e)}function A(e,t,n,r){var o=typeof e;"undefined"!==o&&"boolean"!==o||(e=null);var i=!1;if(null===e)i=!0;else switch(o){case"string":case"number":i=!0;break;case"object":switch(e.$$typeof){case l:case c:i=!0}}if(i)return n(r,e,""===t?"."+D(e,0):t),1;if(i=0,t=""===t?".":t+":",Array.isArray(e))for(var a=0;aN.length&&N.push(e)}function j(e,t,n){return null==e?0:function e(t,n,r,o){var u=typeof t;"undefined"!==u&&"boolean"!==u||(t=null);var l=!1;if(null===t)l=!0;else switch(u){case"string":case"number":l=!0;break;case"object":switch(t.$$typeof){case i:case a:l=!0}}if(l)return r(o,t,""===n?"."+D(t,0):n),1;if(l=0,n=""===n?".":n+":",Array.isArray(t))for(var c=0;cthis.eventPool.length&&this.eventPool.push(e)}function he(e){e.eventPool=[],e.getPooled=pe,e.release=de}o(fe.prototype,{preventDefault:function(){this.defaultPrevented=!0;var e=this.nativeEvent;e&&(e.preventDefault?e.preventDefault():"unknown"!=typeof e.returnValue&&(e.returnValue=!1),this.isDefaultPrevented=ce)},stopPropagation:function(){var e=this.nativeEvent;e&&(e.stopPropagation?e.stopPropagation():"unknown"!=typeof e.cancelBubble&&(e.cancelBubble=!0),this.isPropagationStopped=ce)},persist:function(){this.isPersistent=ce},isPersistent:se,destructor:function(){var e,t=this.constructor.Interface;for(e in t)this[e]=null;this.nativeEvent=this._targetInst=this.dispatchConfig=null,this.isPropagationStopped=this.isDefaultPrevented=se,this._dispatchInstances=this._dispatchListeners=null}}),fe.Interface={type:null,target:null,currentTarget:function(){return null},eventPhase:null,bubbles:null,cancelable:null,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:null,isTrusted:null},fe.extend=function(e){function t(){}function n(){return r.apply(this,arguments)}var r=this;t.prototype=r.prototype;var i=new t;return o(i,n.prototype),n.prototype=i,n.prototype.constructor=n,n.Interface=o({},r.Interface,e),n.extend=r.extend,he(n),n},he(fe);var me=fe.extend({data:null}),ye=fe.extend({data:null}),ve=[9,13,27,32],ge=Y&&"CompositionEvent"in window,be=null;Y&&"documentMode"in document&&(be=document.documentMode);var we=Y&&"TextEvent"in window&&!be,Ee=Y&&(!ge||be&&8=be),xe=String.fromCharCode(32),ke={beforeInput:{phasedRegistrationNames:{bubbled:"onBeforeInput",captured:"onBeforeInputCapture"},dependencies:["compositionend","keypress","textInput","paste"]},compositionEnd:{phasedRegistrationNames:{bubbled:"onCompositionEnd",captured:"onCompositionEndCapture"},dependencies:"blur compositionend keydown keypress keyup mousedown".split(" ")},compositionStart:{phasedRegistrationNames:{bubbled:"onCompositionStart",captured:"onCompositionStartCapture"},dependencies:"blur compositionstart keydown keypress keyup mousedown".split(" ")},compositionUpdate:{phasedRegistrationNames:{bubbled:"onCompositionUpdate",captured:"onCompositionUpdateCapture"},dependencies:"blur compositionupdate keydown keypress keyup mousedown".split(" ")}},Te=!1;function Se(e,t){switch(e){case"keyup":return-1!==ve.indexOf(t.keyCode);case"keydown":return 229!==t.keyCode;case"keypress":case"mousedown":case"blur":return!0;default:return!1}}function Ce(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}var _e=!1;var Oe={eventTypes:ke,extractEvents:function(e,t,n,r){var o=void 0,i=void 0;if(ge)e:{switch(e){case"compositionstart":o=ke.compositionStart;break e;case"compositionend":o=ke.compositionEnd;break e;case"compositionupdate":o=ke.compositionUpdate;break e}o=void 0}else _e?Se(e,n)&&(o=ke.compositionEnd):"keydown"===e&&229===n.keyCode&&(o=ke.compositionStart);return o?(Ee&&"ko"!==n.locale&&(_e||o!==ke.compositionStart?o===ke.compositionEnd&&_e&&(i=le()):(ae="value"in(ie=r)?ie.value:ie.textContent,_e=!0)),o=me.getPooled(o,t,n,r),i?o.data=i:null!==(i=Ce(n))&&(o.data=i),K(o),i=o):i=null,(e=we?function(e,t){switch(e){case"compositionend":return Ce(t);case"keypress":return 32!==t.which?null:(Te=!0,xe);case"textInput":return(e=t.data)===xe&&Te?null:e;default:return null}}(e,n):function(e,t){if(_e)return"compositionend"===e||!ge&&Se(e,t)?(e=le(),ue=ae=ie=null,_e=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1