diff --git a/.circleci/config.yml b/.circleci/config.yml
index 4cba6ae..ea100e5 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -31,5 +31,5 @@ jobs:
command: 'npm start'
background: true
- run:
- name: "Runing tests"
+ name: "Running tests"
command: npx cypress run --record --key $CYPRESS_RECORD_KEY
\ No newline at end of file
diff --git a/.firebase/hosting.YnVpbGQ.cache b/.firebase/hosting.YnVpbGQ.cache
new file mode 100644
index 0000000..74fbd01
--- /dev/null
+++ b/.firebase/hosting.YnVpbGQ.cache
@@ -0,0 +1,30 @@
+asset-manifest.json,1607015017838,637795db4c9bfd5a83b3be695b53eefbf41babc0a6c41be26001a69a491f6f74
+index.html,1607015017838,5a3074114a4d1fef5062cd2eac42e3ba4f0a93a99fa90c2331849ae6564fbc08
+logo192.png,1607014993783,caff018b7f1e8fd481eb1c50d75b0ef236bcd5078b1d15c8bb348453fee30293
+static/css/6.f8d41b26.chunk.css,1607015017835,7ac0851dee7003928162d122ea99fa0f91ca2d6db5f260f79caeddc32d2f1c0d
+favicon.ico,1607014993782,b72f7455f00e4e58792d2bca892abb068e2213838c0316d6b7a0d6d16acd1955
+static/css/6.f8d41b26.chunk.css.map,1607015017834,c9b94518337e29837d8cff7ac2931be2ac01c811cecb2a9d1a2ce641335684b7
+static/css/main.f21e830a.chunk.css,1607015017832,3cca8007a83800f3781e82526aad6f6f1e5c3f5ac22159d570b229ecb9a1e2af
+logo512.png,1607014993789,191fc21360b4ccfb1cda11a1efb97f489ed22672ca83f4064316802bbfdd750e
+manifest.json,1607014993792,341d52628782f8ac9290bbfc43298afccb47b7cbfcee146ae30cf0f46bc30900
+robots.txt,1607014993797,391d14b3c2f8c9143a27a28c7399585142228d4d1bdbe2c87ac946de411fa9a2
+static/js/2.a7db07db.chunk.js.LICENSE.txt,1607015017834,10c6f21b02a3e5df21b9e300b25eded1e0353200e84518db8b2a9e7a3f6fcfe0
+static/css/main.f21e830a.chunk.css.map,1607015017836,0db2a1c52b95862370abc59e0a78ef66d33c430ff681c5cd76698e749f4e7d58
+static/js/3.aec9c59e.chunk.js,1607015017834,1c530ebea53490d63f9b1af418fee0cda9f536bd21bc3d423ae62a95d97d3257
+service-worker.js,1607015017829,240be5c423d354b5bdef2d5db613c5b6e3dd7171e695fc5e48a0d62f97956287
+static/js/4.32f755a1.chunk.js,1607015017834,4dfaa4606163e479d71a11ffe73dd4497f5e3f63566113c557fed2a26d3b4a99
+static/js/5.cc09b653.chunk.js,1607015017835,dc2679a31890ec4bc0c4e77e63f65b5425a7e8562af0368ee4872ac064ccd84a
+static/js/3.aec9c59e.chunk.js.map,1607015017839,46b43a72df7a7e55faa57974c7d53ce46ee6e463b1bbfa45a0924602d0bcd09b
+static/js/6.191246bc.chunk.js,1607015017835,9aadd1b5f10f0347bb0fbb3a34879ded59e7b6db54916e525d0d362866eb400a
+static/js/4.32f755a1.chunk.js.map,1607015017839,a60d0641a940bcc61cbb3ca13a394849bba2811d7bd3e1ba5422d9a92a69e4f4
+static/js/6.191246bc.chunk.js.map,1607015017839,abdc56840e0d82df161534c3b136e246f0957a712457686a7635f1a2d69365ad
+static/js/7.79b372a3.chunk.js,1607015017834,318484a47de83a32dc833d262d39c633389d5547749557fca99c8eee9fdd8d0c
+static/js/7.79b372a3.chunk.js.map,1607015017839,1baca1e1ef9f233b79e085adc12822c1d738e4795592a4490b10657e628482a0
+static/js/main.ebc54cc1.chunk.js,1607015017833,84888735d5f02c1ade2c80b5c06e92683690a46f9435a3232e75c3a7cdbbe948
+static/js/runtime-main.fb277b5b.js,1607015017834,bb90654d55a167f829d48b64677d62c7646562a60084b81c7cf64786e011d862
+static/js/main.ebc54cc1.chunk.js.map,1607015017834,ff645b903412484021173807497d582efa86419125ad580354b704d1470cf19a
+static/js/5.cc09b653.chunk.js.map,1607015017839,a6871eaaa33e64c2431ae7afef4bb59ef5c69c23f6030ab1ea3d7288c20c7e42
+static/js/runtime-main.fb277b5b.js.map,1607015017834,b2a0123c407990521461178849037449ed5f2f3bb9fcc055bd0e242ba13c8b57
+static/js/2.a7db07db.chunk.js,1607015017833,f4cdf46ab9a94e5d6cd51d0ab7c49ba241f79e4b358362611beed13ed2aacffc
+service-worker.js.map,1607015017829,5a94c4d369c4b21cf3dd2122d036d46642394e0cf0c61d77e33537736ce82275
+static/js/2.a7db07db.chunk.js.map,1607015017839,9210ddbad9d8e9a2acce3f2caa819526c47a6abb7e9d8880ce6ce2d7c45b491c
diff --git a/.gitignore b/.gitignore
index c31b89a..3ed3be7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
.eslintcache
+.firebase/**
# dependencies
/node_modules
@@ -22,3 +23,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+
+ .vscode/
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..1a135c2
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,10 @@
+{
+ "json.schemas": [
+ {
+ "fileMatch": [
+ "cypress.json"
+ ],
+ "url": "https://on.cypress.io/cypress.schema.json"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 9b8b5a0..edb074a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3969,6 +3969,11 @@
"shallow-clone": "^3.0.0"
}
},
+ "clsx": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
+ "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA=="
+ },
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -12228,6 +12233,14 @@
"workbox-webpack-plugin": "5.1.4"
}
},
+ "react-toastify": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-7.0.3.tgz",
+ "integrity": "sha512-cxZ5rfurC8LzcZQMTYc8RHIkQTs+BFur18Pzk6Loz6uS8OXUWm6nXVlH/wqglz4Z7UAE8xxcF5mRjfE13487uQ==",
+ "requires": {
+ "clsx": "^1.1.1"
+ }
+ },
"read-pkg": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
diff --git a/package.json b/package.json
index 1536868..8a3eac2 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.1",
+ "react-toastify": "^7.0.3",
"web-vitals": "^0.2.4",
"workbox-background-sync": "^5.1.3",
"workbox-broadcast-update": "^5.1.3",
diff --git a/src/App.js b/src/App.js
index e34eeba..973ffa5 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,6 +1,7 @@
// @ts-nocheck
import React, { Fragment, Suspense, lazy } from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
+import { ToastContainer } from 'react-toastify';
import Skeleton from './components/Skeleton';
import Progress from './components/Progress/Progress';
@@ -80,6 +81,7 @@ function App() {
+
);
diff --git a/src/index.css b/src/index.css
index f6a7c9b..39867bf 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,18 +1,18 @@
-:root{
- --title: #2D3848;
+:root {
+ --title: #2d3848;
--content: #666c75;
--header: #ffffff;
- --body:#ffffff;
- --bg:#ffffff;
+ --body: #ffffff;
+ --bg: #ffffff;
--link: #3854b2;
--skeleton: #eaeaea;
}
-.dark{
+.dark {
--title: #ffffff;
--content: #ffffff;
- --header: #1A202C;
- --body:#3A4556;
- --bg:#2d3747;
+ --header: #1a202c;
+ --body: #3a4556;
+ --bg: #2d3747;
--link: #74befa;
--skeleton: #1d2939;
}
@@ -40,13 +40,25 @@ body {
width: 15px;
}
*::-webkit-scrollbar-thumb {
- background: -webkit-gradient(linear, left top, left bottom, from(var(--skeleton)), to(var(--skeleton)));
+ background: -webkit-gradient(
+ linear,
+ left top,
+ left bottom,
+ from(var(--skeleton)),
+ to(var(--skeleton))
+ );
background: linear-gradient(to bottom, var(--title), var(--title));
border-radius: 30px;
box-shadow: inset 3px 3px 3px var(--title), inset -2px -2px 2px rgba(0, 0, 0, 0.25);
}
*::-webkit-scrollbar-track {
- background: linear-gradient(to right, var(--body), var(--body) 0.0625rem, var(--skeleton) 0.0625rem, var(--skeleton));
+ background: linear-gradient(
+ to right,
+ var(--body),
+ var(--body) 0.0625rem,
+ var(--skeleton) 0.0625rem,
+ var(--skeleton)
+ );
}
.container {
max-width: 980px;
@@ -75,7 +87,7 @@ p {
left: 16px;
right: 16px;
bottom: 16px;
- background-color: #1A202C;
+ background-color: #1a202c;
border-radius: 4px;
min-width: 288px;
padding: 1rem;
@@ -88,6 +100,9 @@ p {
border-radius: 4px;
margin: 0;
}
+.Toastify__toast--dark {
+ background: #323337;
+}
@keyframes dot {
50% {
transform: translateX(96px);
diff --git a/src/index.js b/src/index.js
index fe774bc..ad497a7 100644
--- a/src/index.js
+++ b/src/index.js
@@ -3,8 +3,10 @@ import ReactDOM from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
import './index.css';
import App from './App';
+import { toast } from 'react-toastify';
import * as serviceWorkerRegistration from './serviceWorkerRegistration';
import reportWebVitals from './reportWebVitals';
+import 'react-toastify/dist/ReactToastify.css';
ReactDOM.render(
diff --git a/src/service-worker.js b/src/service-worker.js
index 0f1e0ce..39ae988 100644
--- a/src/service-worker.js
+++ b/src/service-worker.js
@@ -26,47 +26,50 @@ precacheAndRoute(self.__WB_MANIFEST);
// 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 }) => {
- // If this isn't a navigation, skip.
- if (request.mode !== 'navigate') {
- return false;
- } // If this is a URL that starts with /_, skip.
+ // Return false to exempt requests from being fulfilled by index.html.
+ ({ request, 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.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.
+ 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')
+ 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 }),
- ],
- })
+ // 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();
- }
+ if (event.data && event.data.type === 'SKIP_WAITING') {
+ self.skipWaiting();
+ }
});
// Any other custom service worker logic can go here.
+self.addEventListener('install', function (event) {
+ self.skipWaiting();
+});
diff --git a/src/serviceWorkerRegistration.js b/src/serviceWorkerRegistration.js
index 2262ecd..2898641 100644
--- a/src/serviceWorkerRegistration.js
+++ b/src/serviceWorkerRegistration.js
@@ -1,3 +1,4 @@
+import { toast } from 'react-toastify';
// This optional code is used to register a service worker.
// register() is not called by default.
@@ -11,127 +12,148 @@
// 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.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}$/)
+ 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}$/)
);
export function register(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.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;
- }
+ 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.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`;
+ // let isAppOnline = navigator.onLine;
- window.addEventListener('load', () => {
- const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+ // window.addEventListener('online', () => {
+ // if (!isAppOnline) {
+ // // toast('🦄 The connectivity is back, sync in progress...');
+ // isAppOnline = true;
+ // }
+ // });
+ // window.addEventListener('offline', () => {
+ // toast.dark(
+ // 'The app is running offline, any changes mades during this time will be synced as soon as the connectivity is back'
+ // );
+ // isAppOnline = false;
+ // });
+ if (isLocalhost) {
+ // This is running on localhost. Let's check if a service worker still exists or not.
+ checkValidServiceWorker(swUrl, config);
- 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://cra.link/PWA'
- );
- });
- } else {
- // Is not localhost. Just register service worker
- registerValidSW(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://cra.link/PWA'
+ );
+ });
+ } else {
+ // Is not localhost. Just register service worker
+ registerValidSW(swUrl, config);
+ }
+ });
+ }
}
function registerValidSW(swUrl, 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://cra.link/PWA.'
- );
+ 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://cra.link/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.');
+ toast.dark(`Update available! Click to update.`, {
+ position: 'top-right',
+ toastId: 'appUpdateAvailable',
+ onClick: () => {
+ // registration.waiting.postMessage('skipWaiting');
+ window.location.reload();
+ },
+ autoClose: false,
+ });
+ // 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);
- });
+ // Execute callback
+ if (config && config.onSuccess) {
+ config.onSuccess(registration);
+ }
+ }
+ }
+ };
+ };
+ })
+ .catch((error) => {
+ console.error('Error during service worker registration:', error);
+ });
}
function checkValidServiceWorker(swUrl, config) {
- // Check if the service worker can be found. If it can't reload the page.
- 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');
- 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.');
- });
+ // Check if the service worker can be found. If it can't reload the page.
+ 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');
+ 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();
- })
- .catch((error) => {
- console.error(error.message);
- });
- }
+ if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.ready
+ .then((registration) => {
+ registration.unregister();
+ })
+ .catch((error) => {
+ console.error(error.message);
+ });
+ }
}