diff --git a/package-lock.json b/package-lock.json
index f808e79d..27989f2b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2026,9 +2026,9 @@
}
},
"@testing-library/dom": {
- "version": "7.29.2",
- "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.29.2.tgz",
- "integrity": "sha512-CBMELfyY1jKdtLcSRmEnZWRzRkCRVSNPTzhzrn8wY8OnzUo7Pe/W+HgLzt4TDnWIPYeusHBodf9wUjJF48kPmA==",
+ "version": "7.29.4",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.29.4.tgz",
+ "integrity": "sha512-CtrJRiSYEfbtNGtEsd78mk1n1v2TUbeABlNIcOCJdDfkN5/JTOwQEbbQpoSRxGqzcWPgStMvJ4mNolSuBRv1NA==",
"requires": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
@@ -2041,9 +2041,9 @@
},
"dependencies": {
"@babel/runtime": {
- "version": "7.12.5",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz",
- "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.13.tgz",
+ "integrity": "sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
@@ -2162,18 +2162,18 @@
}
},
"@testing-library/react": {
- "version": "11.2.3",
- "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.3.tgz",
- "integrity": "sha512-BirBUGPkTW28ULuCwIbYo0y2+0aavHczBT6N9r3LrsswEW3pg25l1wgoE7I8QBIy1upXWkwKpYdWY7NYYP0Bxw==",
+ "version": "11.2.5",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.5.tgz",
+ "integrity": "sha512-yEx7oIa/UWLe2F2dqK0FtMF9sJWNXD+2PPtp39BvE0Kh9MJ9Kl0HrZAgEuhUJR+Lx8Di6Xz+rKwSdEPY2UV8ZQ==",
"requires": {
"@babel/runtime": "^7.12.5",
"@testing-library/dom": "^7.28.1"
},
"dependencies": {
"@babel/runtime": {
- "version": "7.12.5",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz",
- "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.13.tgz",
+ "integrity": "sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
@@ -2186,17 +2186,17 @@
}
},
"@testing-library/user-event": {
- "version": "12.6.2",
- "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.6.2.tgz",
- "integrity": "sha512-4OsiTSo2vbQm+eOnm1un8b9i2Re4mn+D7d7ET6HXtzYKY7vPe3O01iYKRmSW9vS5mNrQcCLwvRhVq1gWs5YGKA==",
+ "version": "12.6.3",
+ "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.6.3.tgz",
+ "integrity": "sha512-PCmbUKofE4SXA7l8jphZAbvv5H3c4ix34xPZ/GNe99fASX//msJRgiMbHIBP+GwRfgVG9c7zmkODSPu2X2vNRw==",
"requires": {
"@babel/runtime": "^7.12.5"
},
"dependencies": {
"@babel/runtime": {
- "version": "7.12.5",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz",
- "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==",
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.13.tgz",
+ "integrity": "sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
@@ -2391,9 +2391,9 @@
"integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug=="
},
"@types/react": {
- "version": "17.0.0",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.0.tgz",
- "integrity": "sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw==",
+ "version": "17.0.1",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.1.tgz",
+ "integrity": "sha512-w8t9f53B2ei4jeOqf/gxtc2Sswnc3LBK5s0DyJcg5xd10tMHXts2N31cKjWfH9IC/JvEPa/YF1U4YeP1t4R6HQ==",
"requires": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
@@ -16522,6 +16522,11 @@
"minimalistic-assert": "^1.0.0"
}
},
+ "web-vitals": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-0.2.4.tgz",
+ "integrity": "sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg=="
+ },
"webidl-conversions": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
diff --git a/package.json b/package.json
index d400cc96..dbd561ec 100644
--- a/package.json
+++ b/package.json
@@ -6,11 +6,11 @@
"dependencies": {
"@reduxjs/toolkit": "^1.5.0",
"@testing-library/jest-dom": "^5.11.9",
- "@testing-library/react": "^11.1.0",
- "@testing-library/user-event": "^12.6.2",
+ "@testing-library/react": "^11.2.5",
+ "@testing-library/user-event": "^12.6.3",
"@types/jest": "^26.0.19",
"@types/node": "^14.14.22",
- "@types/react": "^17.0.0",
+ "@types/react": "^17.0.1",
"@types/react-document-title": "^2.0.4",
"@types/react-dom": "^17.0.0",
"@types/react-loadable": "^5.5.4",
@@ -36,7 +36,13 @@
"react-scripts": "^4.0.1",
"redux": "^4.0.5",
"redux-persist": "^6.0.0",
- "typescript": "^4.1.3"
+ "typescript": "^4.1.3",
+ "web-vitals": "^0.2.4",
+ "workbox-core": "^5.1.3",
+ "workbox-expiration": "^5.1.3",
+ "workbox-precaching": "^5.1.3",
+ "workbox-routing": "^5.1.3",
+ "workbox-strategies": "^5.1.3"
},
"scripts": {
"build": "react-scripts build",
diff --git a/public/manifest.json b/public/manifest.json
index bd7bfa8c..fb538eb2 100644
--- a/public/manifest.json
+++ b/public/manifest.json
@@ -22,7 +22,7 @@
"purpose": "maskable"
}
],
- "start_url": "./",
+ "start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
diff --git a/src/index.tsx b/src/index.tsx
index c802b822..f96554d9 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,25 +1,39 @@
-import { createBrowserHistory } from 'history';
-import ReactDOM from 'react-dom';
-import { PersistGate } from 'redux-persist/integration/react'
-import { Provider } from 'react-redux';
-import { Router } from 'react-router-dom';
-import App from './App';
-import * as serviceWorker from './serviceWorker';
-import configureStore from './Store/configureStore';
-import './index.scss';
+import { createBrowserHistory } from "history";
+import React from "react";
+import ReactDOM from "react-dom";
+import { PersistGate } from "redux-persist/integration/react";
+import { Provider } from "react-redux";
+import { Router } from "react-router-dom";
+import App from "./App";
+import * as serviceWorkerRegistration from "./serviceWorkerRegistration";
+import reportWebVitals from "./reportWebVitals";
+import configureStore from "./Store/configureStore";
+import "./index.scss";
-const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href') || '';
+const baseUrl =
+ document.getElementsByTagName("base")[0].getAttribute("href") || "";
const history = createBrowserHistory({ basename: baseUrl });
const { store, persistor } = configureStore();
ReactDOM.render(
-
-
-
-
-
-
- ,
- document.getElementById('root'));
+
+
+
+
+
+
+
+
+ ,
+ document.getElementById("root")
+);
-serviceWorker.register();
+// 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://cra.link/PWA
+serviceWorkerRegistration.register();
+
+// If you want to start measuring performance in your app, pass a function
+// to log results (for example: reportWebVitals(console.log))
+// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
+reportWebVitals();
diff --git a/src/reportWebVitals.ts b/src/reportWebVitals.ts
new file mode 100644
index 00000000..5fa3583b
--- /dev/null
+++ b/src/reportWebVitals.ts
@@ -0,0 +1,15 @@
+import { ReportHandler } from "web-vitals";
+
+const reportWebVitals = (onPerfEntry?: ReportHandler) => {
+ if (onPerfEntry && onPerfEntry instanceof Function) {
+ import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
+ getCLS(onPerfEntry);
+ getFID(onPerfEntry);
+ getFCP(onPerfEntry);
+ getLCP(onPerfEntry);
+ getTTFB(onPerfEntry);
+ });
+ }
+};
+
+export default reportWebVitals;
diff --git a/src/service-worker.ts b/src/service-worker.ts
new file mode 100644
index 00000000..5be3706e
--- /dev/null
+++ b/src/service-worker.ts
@@ -0,0 +1,81 @@
+///
+/* eslint-disable no-restricted-globals */
+
+// This service worker can be customized!
+// See https://developers.google.com/web/tools/workbox/modules
+// for the list of available Workbox modules, or add any other
+// code you'd like.
+// You can also remove this file if you'd prefer not to use a
+// service worker, and the Workbox build step will be skipped.
+
+import { clientsClaim } from "workbox-core";
+import { ExpirationPlugin } from "workbox-expiration";
+import { precacheAndRoute, createHandlerBoundToURL } from "workbox-precaching";
+import { registerRoute } from "workbox-routing";
+import { StaleWhileRevalidate } from "workbox-strategies";
+
+declare const self: ServiceWorkerGlobalScope;
+
+clientsClaim();
+
+// Precache all of the assets generated by your build process.
+// Their URLs are injected into the manifest variable below.
+// This variable must be present somewhere in your service worker file,
+// even if you decide not to use precaching. See https://cra.link/PWA
+precacheAndRoute(self.__WB_MANIFEST);
+
+// Set up App Shell-style routing, so that all navigation requests
+// are fulfilled with your index.html shell. Learn more at
+// https://developers.google.com/web/fundamentals/architecture/app-shell
+const fileExtensionRegexp = new RegExp("/[^/?]+\\.[^/]+$");
+registerRoute(
+ // Return false to exempt requests from being fulfilled by index.html.
+ ({ request, url }: { request: Request; url: URL }) => {
+ // If this isn't a navigation, skip.
+ if (request.mode !== "navigate") {
+ return false;
+ }
+
+ // If this is a URL that starts with /_, skip.
+ if (url.pathname.startsWith("/_")) {
+ return false;
+ }
+
+ // If this looks like a URL for a resource, because it contains
+ // a file extension, skip.
+ if (url.pathname.match(fileExtensionRegexp)) {
+ return false;
+ }
+
+ // Return true to signal that we want to use the handler.
+ return true;
+ },
+ createHandlerBoundToURL(process.env.PUBLIC_URL + "/index.html")
+);
+
+// An example runtime caching route for requests that aren't handled by the
+// precache, in this case same-origin .png requests like those from in public/
+registerRoute(
+ // Add in any other file extensions or routing criteria as needed.
+ ({ url }) =>
+ url.origin === self.location.origin && url.pathname.endsWith(".png"),
+ // Customize this strategy as needed, e.g., by changing to CacheFirst.
+ new StaleWhileRevalidate({
+ cacheName: "images",
+ plugins: [
+ // Ensure that once this runtime cache reaches a maximum size the
+ // least-recently used images are removed.
+ new ExpirationPlugin({ maxEntries: 50 }),
+ ],
+ })
+);
+
+// This allows the web app to trigger skipWaiting via
+// registration.waiting.postMessage({type: 'SKIP_WAITING'})
+self.addEventListener("message", (event) => {
+ if (event.data && event.data.type === "SKIP_WAITING") {
+ self.skipWaiting();
+ }
+});
+
+// Any other custom service worker logic can go here.
diff --git a/src/serviceWorker.ts b/src/serviceWorkerRegistration.ts
similarity index 67%
rename from src/serviceWorker.ts
rename to src/serviceWorkerRegistration.ts
index 5b4db48c..6d599917 100644
--- a/src/serviceWorker.ts
+++ b/src/serviceWorkerRegistration.ts
@@ -8,16 +8,16 @@
// 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
+// opt-in, read https://cra.link/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}$/
- )
+ window.location.hostname === "localhost" ||
+ // [::1] is the IPv6 localhost address.
+ window.location.hostname === "[::1]" ||
+ // 127.0.0.0/8 are 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 = {
@@ -26,12 +26,9 @@ type Config = {
};
export function register(config?: Config) {
- if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+ 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
- );
+ const publicUrl = new URL(process.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
@@ -39,7 +36,7 @@ export function register(config?: Config) {
return;
}
- window.addEventListener('load', () => {
+ window.addEventListener("load", () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
@@ -49,10 +46,9 @@ export function register(config?: Config) {
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
- // tslint:disable-next-line:no-console
console.log(
- 'This web app is being served cache-first by a service ' +
- 'worker. To learn more, visit https://bit.ly/CRA-PWA'
+ "This web app is being served cache-first by a service " +
+ "worker. To learn more, visit https://cra.link/PWA"
);
});
} else {
@@ -66,22 +62,21 @@ export function register(config?: Config) {
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
- .then(registration => {
+ .then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
- if (installingWorker.state === 'installed') {
+ 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.
- // tslint:disable-next-line:no-console
console.log(
- 'New content is available and will be used when all ' +
- 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
+ "New content is available and will be used when all " +
+ "tabs for this page are closed. See https://cra.link/PWA."
);
// Execute callback
@@ -92,8 +87,7 @@ function registerValidSW(swUrl: string, config?: Config) {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
- // tslint:disable-next-line:no-console
- console.log('Content is cached for offline use.');
+ console.log("Content is cached for offline use.");
// Execute callback
if (config && config.onSuccess) {
@@ -104,24 +98,25 @@ function registerValidSW(swUrl: string, config?: Config) {
};
};
})
- .catch(error => {
- // tslint:disable-next-line:no-console
- console.error('Error during service worker registration:', error);
+ .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 => {
+ fetch(swUrl, {
+ headers: { "Service-Worker": "script" },
+ })
+ .then((response) => {
// Ensure service worker exists, and that we really are getting a JS file.
- const contentType = response.headers.get('content-type');
+ const contentType = response.headers.get("content-type");
if (
response.status === 404 ||
- (contentType != null && contentType.indexOf('javascript') === -1)
+ (contentType != null && contentType.indexOf("javascript") === -1)
) {
// No service worker found. Probably a different app. Reload the page.
- navigator.serviceWorker.ready.then(registration => {
+ navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => {
window.location.reload();
});
@@ -132,17 +127,20 @@ function checkValidServiceWorker(swUrl: string, config?: Config) {
}
})
.catch(() => {
- // tslint:disable-next-line:no-console
console.log(
- 'No internet connection found. App is running in offline mode.'
+ "No internet connection found. App is running in offline mode."
);
});
}
export function unregister() {
- if ('serviceWorker' in navigator) {
- navigator.serviceWorker.ready.then(registration => {
- registration.unregister();
- });
+ if ("serviceWorker" in navigator) {
+ navigator.serviceWorker.ready
+ .then((registration) => {
+ registration.unregister();
+ })
+ .catch((error) => {
+ console.error(error.message);
+ });
}
}