Browse Source

Initial commit

Sergio Mattei 2 years ago
commit
22f1c1f1d7

+ 29
- 0
.gitignore View File

@@ -0,0 +1,29 @@
1
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+# dependencies
4
+/node_modules
5
+/.pnp
6
+.pnp.js
7
+
8
+# testing
9
+/coverage
10
+
11
+# production
12
+/build
13
+
14
+# misc
15
+.DS_Store
16
+.env.local
17
+.env.development.local
18
+.env.test.local
19
+.env.production.local
20
+/.vscode/*
21
+!/.vscode/extensions.json
22
+.idea
23
+
24
+npm-debug.log*
25
+yarn-debug.log*
26
+yarn-error.log*
27
+
28
+# Optional eslint cache
29
+.eslintcache

+ 5
- 0
.vscode/extensions.json View File

@@ -0,0 +1,5 @@
1
+{
2
+    "recommendations": [
3
+        "ionic.ionic"
4
+    ]
5
+}

+ 10
- 0
capacitor.config.ts View File

@@ -0,0 +1,10 @@
1
+import { CapacitorConfig } from '@capacitor/cli';
2
+
3
+const config: CapacitorConfig = {
4
+  appId: 'io.ionic.starter',
5
+  appName: 'renacer',
6
+  webDir: 'build',
7
+  bundledWebRuntime: false
8
+};
9
+
10
+export default config;

+ 7
- 0
ionic.config.json View File

@@ -0,0 +1,7 @@
1
+{
2
+  "name": "renacer",
3
+  "integrations": {
4
+    "capacitor": {}
5
+  },
6
+  "type": "react"
7
+}

+ 27723
- 0
package-lock.json
File diff suppressed because it is too large
View File


+ 72
- 0
package.json View File

@@ -0,0 +1,72 @@
1
+{
2
+  "name": "renacer",
3
+  "version": "0.0.1",
4
+  "private": true,
5
+  "dependencies": {
6
+    "@capacitor/app": "4.1.0",
7
+    "@capacitor/core": "4.4.0",
8
+    "@capacitor/haptics": "4.0.1",
9
+    "@capacitor/keyboard": "4.0.1",
10
+    "@capacitor/status-bar": "4.0.1",
11
+    "@ionic/react": "^6.0.0",
12
+    "@ionic/react-router": "^6.0.0",
13
+    "@testing-library/jest-dom": "^5.11.9",
14
+    "@testing-library/react": "^13.3.0",
15
+    "@testing-library/user-event": "^12.6.3",
16
+    "@types/jest": "^26.0.20",
17
+    "@types/node": "^12.19.15",
18
+    "@types/react": "^18.0.17",
19
+    "@types/react-dom": "^18.0.6",
20
+    "@types/react-router": "^5.1.11",
21
+    "@types/react-router-dom": "^5.1.7",
22
+    "history": "^4.9.0",
23
+    "ionicons": "^6.0.3",
24
+    "react": "^18.2.0",
25
+    "react-dom": "^18.2.0",
26
+    "react-router": "^5.2.0",
27
+    "react-router-dom": "^5.2.0",
28
+    "react-scripts": "^5.0.0",
29
+    "typescript": "^4.1.3",
30
+    "web-vitals": "^0.2.4",
31
+    "workbox-background-sync": "^5.1.4",
32
+    "workbox-broadcast-update": "^5.1.4",
33
+    "workbox-cacheable-response": "^5.1.4",
34
+    "workbox-core": "^5.1.4",
35
+    "workbox-expiration": "^5.1.4",
36
+    "workbox-google-analytics": "^5.1.4",
37
+    "workbox-navigation-preload": "^5.1.4",
38
+    "workbox-precaching": "^5.1.4",
39
+    "workbox-range-requests": "^5.1.4",
40
+    "workbox-routing": "^5.1.4",
41
+    "workbox-strategies": "^5.1.4",
42
+    "workbox-streams": "^5.1.4"
43
+  },
44
+  "scripts": {
45
+    "start": "react-scripts start",
46
+    "build": "react-scripts build",
47
+    "test": "react-scripts test --transformIgnorePatterns 'node_modules/(?!(@ionic/react|@ionic/react-router|@ionic/core|@stencil/core|ionicons)/)'",
48
+    "eject": "react-scripts eject"
49
+  },
50
+  "eslintConfig": {
51
+    "extends": [
52
+      "react-app",
53
+      "react-app/jest"
54
+    ]
55
+  },
56
+  "browserslist": {
57
+    "production": [
58
+      ">0.2%",
59
+      "not dead",
60
+      "not op_mini all"
61
+    ],
62
+    "development": [
63
+      "last 1 chrome version",
64
+      "last 1 firefox version",
65
+      "last 1 safari version"
66
+    ]
67
+  },
68
+  "devDependencies": {
69
+    "@capacitor/cli": "4.4.0"
70
+  },
71
+  "description": "An Ionic project"
72
+}

BIN
public/assets/icon/favicon.png View File


BIN
public/assets/icon/icon.png View File


+ 1
- 0
public/assets/shapes.svg View File

@@ -0,0 +1 @@
1
+<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>

+ 31
- 0
public/index.html View File

@@ -0,0 +1,31 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+  <head>
4
+    <meta charset="utf-8" />
5
+    <title>Ionic App</title>
6
+
7
+    <base href="/" />
8
+
9
+    <meta name="color-scheme" content="light dark" />
10
+    <meta
11
+      name="viewport"
12
+      content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
13
+    />
14
+    <meta name="format-detection" content="telephone=no" />
15
+    <meta name="msapplication-tap-highlight" content="no" />
16
+
17
+    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
18
+
19
+    <link rel="shortcut icon" type="image/png" href="%PUBLIC_URL%/assets/icon/favicon.png" />
20
+
21
+    <!-- add to homescreen for ios -->
22
+    <meta name="apple-mobile-web-app-capable" content="yes" />
23
+    <meta name="apple-mobile-web-app-title" content="Ionic App" />
24
+    <meta name="apple-mobile-web-app-status-bar-style" content="black" />
25
+  </head>
26
+
27
+  <body>
28
+    <div id="root"></div>
29
+  </body>
30
+
31
+</html>

+ 21
- 0
public/manifest.json View File

@@ -0,0 +1,21 @@
1
+{
2
+  "short_name": "Ionic App",
3
+  "name": "My Ionic App",
4
+  "icons": [
5
+    {
6
+      "src": "assets/icon/favicon.png",
7
+      "sizes": "64x64 32x32 24x24 16x16",
8
+      "type": "image/x-icon"
9
+    },
10
+    {
11
+      "src": "assets/icon/icon.png",
12
+      "type": "image/png",
13
+      "sizes": "512x512",
14
+      "purpose": "maskable"
15
+    }
16
+  ],
17
+  "start_url": ".",
18
+  "display": "standalone",
19
+  "theme_color": "#ffffff",
20
+  "background_color": "#ffffff"
21
+}

+ 8
- 0
src/App.test.tsx View File

@@ -0,0 +1,8 @@
1
+import React from 'react';
2
+import { render } from '@testing-library/react';
3
+import App from './App';
4
+
5
+test('renders without crashing', () => {
6
+  const { baseElement } = render(<App />);
7
+  expect(baseElement).toBeDefined();
8
+});

+ 76
- 0
src/App.tsx View File

@@ -0,0 +1,76 @@
1
+import { Redirect, Route } from 'react-router-dom';
2
+import {
3
+  IonApp,
4
+  IonIcon,
5
+  IonLabel,
6
+  IonRouterOutlet,
7
+  IonTabBar,
8
+  IonTabButton,
9
+  IonTabs,
10
+  setupIonicReact
11
+} from '@ionic/react';
12
+import { IonReactRouter } from '@ionic/react-router';
13
+import { ellipse, square, triangle } from 'ionicons/icons';
14
+import Tab1 from './pages/Tab1';
15
+import Tab2 from './pages/Tab2';
16
+import Tab3 from './pages/Tab3';
17
+
18
+/* Core CSS required for Ionic components to work properly */
19
+import '@ionic/react/css/core.css';
20
+
21
+/* Basic CSS for apps built with Ionic */
22
+import '@ionic/react/css/normalize.css';
23
+import '@ionic/react/css/structure.css';
24
+import '@ionic/react/css/typography.css';
25
+
26
+/* Optional CSS utils that can be commented out */
27
+import '@ionic/react/css/padding.css';
28
+import '@ionic/react/css/float-elements.css';
29
+import '@ionic/react/css/text-alignment.css';
30
+import '@ionic/react/css/text-transformation.css';
31
+import '@ionic/react/css/flex-utils.css';
32
+import '@ionic/react/css/display.css';
33
+
34
+/* Theme variables */
35
+import './theme/variables.css';
36
+
37
+setupIonicReact();
38
+
39
+const App: React.FC = () => (
40
+  <IonApp>
41
+    <IonReactRouter>
42
+      <IonTabs>
43
+        <IonRouterOutlet>
44
+          <Route exact path="/tab1">
45
+            <Tab1 />
46
+          </Route>
47
+          <Route exact path="/tab2">
48
+            <Tab2 />
49
+          </Route>
50
+          <Route path="/tab3">
51
+            <Tab3 />
52
+          </Route>
53
+          <Route exact path="/">
54
+            <Redirect to="/tab1" />
55
+          </Route>
56
+        </IonRouterOutlet>
57
+        <IonTabBar slot="bottom">
58
+          <IonTabButton tab="tab1" href="/tab1">
59
+            <IonIcon icon={triangle} />
60
+            <IonLabel>Tab 1</IonLabel>
61
+          </IonTabButton>
62
+          <IonTabButton tab="tab2" href="/tab2">
63
+            <IonIcon icon={ellipse} />
64
+            <IonLabel>Tab 2</IonLabel>
65
+          </IonTabButton>
66
+          <IonTabButton tab="tab3" href="/tab3">
67
+            <IonIcon icon={square} />
68
+            <IonLabel>Tab 3</IonLabel>
69
+          </IonTabButton>
70
+        </IonTabBar>
71
+      </IonTabs>
72
+    </IonReactRouter>
73
+  </IonApp>
74
+);
75
+
76
+export default App;

+ 24
- 0
src/components/ExploreContainer.css View File

@@ -0,0 +1,24 @@
1
+.container {
2
+  text-align: center;
3
+  position: absolute;
4
+  left: 0;
5
+  right: 0;
6
+  top: 50%;
7
+  transform: translateY(-50%);
8
+}
9
+
10
+.container strong {
11
+  font-size: 20px;
12
+  line-height: 26px;
13
+}
14
+
15
+.container p {
16
+  font-size: 16px;
17
+  line-height: 22px;
18
+  color: #8c8c8c;
19
+  margin: 0;
20
+}
21
+
22
+.container a {
23
+  text-decoration: none;
24
+}

+ 16
- 0
src/components/ExploreContainer.tsx View File

@@ -0,0 +1,16 @@
1
+import './ExploreContainer.css';
2
+
3
+interface ContainerProps {
4
+  name: string;
5
+}
6
+
7
+const ExploreContainer: React.FC<ContainerProps> = ({ name }) => {
8
+  return (
9
+    <div className="container">
10
+      <strong>{name}</strong>
11
+      <p>Explore <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
12
+    </div>
13
+  );
14
+};
15
+
16
+export default ExploreContainer;

+ 23
- 0
src/index.tsx View File

@@ -0,0 +1,23 @@
1
+import React from 'react';
2
+import { createRoot } from 'react-dom/client';
3
+import App from './App';
4
+import * as serviceWorkerRegistration from './serviceWorkerRegistration';
5
+import reportWebVitals from './reportWebVitals';
6
+
7
+const container = document.getElementById('root');
8
+const root = createRoot(container!);
9
+root.render(
10
+  <React.StrictMode>
11
+    <App />
12
+  </React.StrictMode>
13
+);
14
+
15
+// If you want your app to work offline and load faster, you can change
16
+// unregister() to register() below. Note this comes with some pitfalls.
17
+// Learn more about service workers: https://cra.link/PWA
18
+serviceWorkerRegistration.unregister();
19
+
20
+// If you want to start measuring performance in your app, pass a function
21
+// to log results (for example: reportWebVitals(console.log))
22
+// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
23
+reportWebVitals();

+ 0
- 0
src/pages/Tab1.css View File


+ 25
- 0
src/pages/Tab1.tsx View File

@@ -0,0 +1,25 @@
1
+import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
2
+import ExploreContainer from '../components/ExploreContainer';
3
+import './Tab1.css';
4
+
5
+const Tab1: React.FC = () => {
6
+  return (
7
+    <IonPage>
8
+      <IonHeader>
9
+        <IonToolbar>
10
+          <IonTitle>Tab 1</IonTitle>
11
+        </IonToolbar>
12
+      </IonHeader>
13
+      <IonContent fullscreen>
14
+        <IonHeader collapse="condense">
15
+          <IonToolbar>
16
+            <IonTitle size="large">Tab 1</IonTitle>
17
+          </IonToolbar>
18
+        </IonHeader>
19
+        <ExploreContainer name="Tab 1 page" />
20
+      </IonContent>
21
+    </IonPage>
22
+  );
23
+};
24
+
25
+export default Tab1;

+ 0
- 0
src/pages/Tab2.css View File


+ 25
- 0
src/pages/Tab2.tsx View File

@@ -0,0 +1,25 @@
1
+import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
2
+import ExploreContainer from '../components/ExploreContainer';
3
+import './Tab2.css';
4
+
5
+const Tab2: React.FC = () => {
6
+  return (
7
+    <IonPage>
8
+      <IonHeader>
9
+        <IonToolbar>
10
+          <IonTitle>Tab 2</IonTitle>
11
+        </IonToolbar>
12
+      </IonHeader>
13
+      <IonContent fullscreen>
14
+        <IonHeader collapse="condense">
15
+          <IonToolbar>
16
+            <IonTitle size="large">Tab 2</IonTitle>
17
+          </IonToolbar>
18
+        </IonHeader>
19
+        <ExploreContainer name="Tab 2 page" />
20
+      </IonContent>
21
+    </IonPage>
22
+  );
23
+};
24
+
25
+export default Tab2;

+ 0
- 0
src/pages/Tab3.css View File


+ 25
- 0
src/pages/Tab3.tsx View File

@@ -0,0 +1,25 @@
1
+import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
2
+import ExploreContainer from '../components/ExploreContainer';
3
+import './Tab3.css';
4
+
5
+const Tab3: React.FC = () => {
6
+  return (
7
+    <IonPage>
8
+      <IonHeader>
9
+        <IonToolbar>
10
+          <IonTitle>Tab 3</IonTitle>
11
+        </IonToolbar>
12
+      </IonHeader>
13
+      <IonContent fullscreen>
14
+        <IonHeader collapse="condense">
15
+          <IonToolbar>
16
+            <IonTitle size="large">Tab 3</IonTitle>
17
+          </IonToolbar>
18
+        </IonHeader>
19
+        <ExploreContainer name="Tab 3 page" />
20
+      </IonContent>
21
+    </IonPage>
22
+  );
23
+};
24
+
25
+export default Tab3;

+ 1
- 0
src/react-app-env.d.ts View File

@@ -0,0 +1 @@
1
+/// <reference types="react-scripts" />

+ 15
- 0
src/reportWebVitals.ts View File

@@ -0,0 +1,15 @@
1
+import { ReportHandler } from 'web-vitals';
2
+
3
+const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4
+  if (onPerfEntry && onPerfEntry instanceof Function) {
5
+    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6
+      getCLS(onPerfEntry);
7
+      getFID(onPerfEntry);
8
+      getFCP(onPerfEntry);
9
+      getLCP(onPerfEntry);
10
+      getTTFB(onPerfEntry);
11
+    });
12
+  }
13
+};
14
+
15
+export default reportWebVitals;

+ 80
- 0
src/service-worker.ts View File

@@ -0,0 +1,80 @@
1
+/// <reference lib="webworker" />
2
+/* eslint-disable no-restricted-globals */
3
+
4
+// This service worker can be customized!
5
+// See https://developers.google.com/web/tools/workbox/modules
6
+// for the list of available Workbox modules, or add any other
7
+// code you'd like.
8
+// You can also remove this file if you'd prefer not to use a
9
+// service worker, and the Workbox build step will be skipped.
10
+
11
+import { clientsClaim } from 'workbox-core';
12
+import { ExpirationPlugin } from 'workbox-expiration';
13
+import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
14
+import { registerRoute } from 'workbox-routing';
15
+import { StaleWhileRevalidate } from 'workbox-strategies';
16
+
17
+declare const self: ServiceWorkerGlobalScope;
18
+
19
+clientsClaim();
20
+
21
+// Precache all of the assets generated by your build process.
22
+// Their URLs are injected into the manifest variable below.
23
+// This variable must be present somewhere in your service worker file,
24
+// even if you decide not to use precaching. See https://cra.link/PWA
25
+precacheAndRoute(self.__WB_MANIFEST);
26
+
27
+// Set up App Shell-style routing, so that all navigation requests
28
+// are fulfilled with your index.html shell. Learn more at
29
+// https://developers.google.com/web/fundamentals/architecture/app-shell
30
+const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
31
+registerRoute(
32
+  // Return false to exempt requests from being fulfilled by index.html.
33
+  ({ request, url }: { request: Request; url: URL }) => {
34
+    // If this isn't a navigation, skip.
35
+    if (request.mode !== 'navigate') {
36
+      return false;
37
+    }
38
+
39
+    // If this is a URL that starts with /_, skip.
40
+    if (url.pathname.startsWith('/_')) {
41
+      return false;
42
+    }
43
+
44
+    // If this looks like a URL for a resource, because it contains
45
+    // a file extension, skip.
46
+    if (url.pathname.match(fileExtensionRegexp)) {
47
+      return false;
48
+    }
49
+
50
+    // Return true to signal that we want to use the handler.
51
+    return true;
52
+  },
53
+  createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
54
+);
55
+
56
+// An example runtime caching route for requests that aren't handled by the
57
+// precache, in this case same-origin .png requests like those from in public/
58
+registerRoute(
59
+  // Add in any other file extensions or routing criteria as needed.
60
+  ({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'),
61
+  // Customize this strategy as needed, e.g., by changing to CacheFirst.
62
+  new StaleWhileRevalidate({
63
+    cacheName: 'images',
64
+    plugins: [
65
+      // Ensure that once this runtime cache reaches a maximum size the
66
+      // least-recently used images are removed.
67
+      new ExpirationPlugin({ maxEntries: 50 }),
68
+    ],
69
+  })
70
+);
71
+
72
+// This allows the web app to trigger skipWaiting via
73
+// registration.waiting.postMessage({type: 'SKIP_WAITING'})
74
+self.addEventListener('message', (event) => {
75
+  if (event.data && event.data.type === 'SKIP_WAITING') {
76
+    self.skipWaiting();
77
+  }
78
+});
79
+
80
+// Any other custom service worker logic can go here.

+ 142
- 0
src/serviceWorkerRegistration.ts View File

@@ -0,0 +1,142 @@
1
+// This optional code is used to register a service worker.
2
+// register() is not called by default.
3
+
4
+// This lets the app load faster on subsequent visits in production, and gives
5
+// it offline capabilities. However, it also means that developers (and users)
6
+// will only see deployed updates on subsequent visits to a page, after all the
7
+// existing tabs open on the page have been closed, since previously cached
8
+// resources are updated in the background.
9
+
10
+// To learn more about the benefits of this model and instructions on how to
11
+// opt-in, read https://cra.link/PWA
12
+
13
+const isLocalhost = Boolean(
14
+  window.location.hostname === 'localhost' ||
15
+    // [::1] is the IPv6 localhost address.
16
+    window.location.hostname === '[::1]' ||
17
+    // 127.0.0.0/8 are considered localhost for IPv4.
18
+    window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
19
+);
20
+
21
+type Config = {
22
+  onSuccess?: (registration: ServiceWorkerRegistration) => void;
23
+  onUpdate?: (registration: ServiceWorkerRegistration) => void;
24
+};
25
+
26
+export function register(config?: Config) {
27
+  if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
28
+    // The URL constructor is available in all browsers that support SW.
29
+    const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
30
+    if (publicUrl.origin !== window.location.origin) {
31
+      // Our service worker won't work if PUBLIC_URL is on a different origin
32
+      // from what our page is served on. This might happen if a CDN is used to
33
+      // serve assets; see https://github.com/facebook/create-react-app/issues/2374
34
+      return;
35
+    }
36
+
37
+    window.addEventListener('load', () => {
38
+      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
39
+
40
+      if (isLocalhost) {
41
+        // This is running on localhost. Let's check if a service worker still exists or not.
42
+        checkValidServiceWorker(swUrl, config);
43
+
44
+        // Add some additional logging to localhost, pointing developers to the
45
+        // service worker/PWA documentation.
46
+        navigator.serviceWorker.ready.then(() => {
47
+          console.log(
48
+            'This web app is being served cache-first by a service ' +
49
+              'worker. To learn more, visit https://cra.link/PWA'
50
+          );
51
+        });
52
+      } else {
53
+        // Is not localhost. Just register service worker
54
+        registerValidSW(swUrl, config);
55
+      }
56
+    });
57
+  }
58
+}
59
+
60
+function registerValidSW(swUrl: string, config?: Config) {
61
+  navigator.serviceWorker
62
+    .register(swUrl)
63
+    .then((registration) => {
64
+      registration.onupdatefound = () => {
65
+        const installingWorker = registration.installing;
66
+        if (installingWorker == null) {
67
+          return;
68
+        }
69
+        installingWorker.onstatechange = () => {
70
+          if (installingWorker.state === 'installed') {
71
+            if (navigator.serviceWorker.controller) {
72
+              // At this point, the updated precached content has been fetched,
73
+              // but the previous service worker will still serve the older
74
+              // content until all client tabs are closed.
75
+              console.log(
76
+                'New content is available and will be used when all ' +
77
+                  'tabs for this page are closed. See https://cra.link/PWA.'
78
+              );
79
+
80
+              // Execute callback
81
+              if (config && config.onUpdate) {
82
+                config.onUpdate(registration);
83
+              }
84
+            } else {
85
+              // At this point, everything has been precached.
86
+              // It's the perfect time to display a
87
+              // "Content is cached for offline use." message.
88
+              console.log('Content is cached for offline use.');
89
+
90
+              // Execute callback
91
+              if (config && config.onSuccess) {
92
+                config.onSuccess(registration);
93
+              }
94
+            }
95
+          }
96
+        };
97
+      };
98
+    })
99
+    .catch((error) => {
100
+      console.error('Error during service worker registration:', error);
101
+    });
102
+}
103
+
104
+function checkValidServiceWorker(swUrl: string, config?: Config) {
105
+  // Check if the service worker can be found. If it can't reload the page.
106
+  fetch(swUrl, {
107
+    headers: { 'Service-Worker': 'script' },
108
+  })
109
+    .then((response) => {
110
+      // Ensure service worker exists, and that we really are getting a JS file.
111
+      const contentType = response.headers.get('content-type');
112
+      if (
113
+        response.status === 404 ||
114
+        (contentType != null && contentType.indexOf('javascript') === -1)
115
+      ) {
116
+        // No service worker found. Probably a different app. Reload the page.
117
+        navigator.serviceWorker.ready.then((registration) => {
118
+          registration.unregister().then(() => {
119
+            window.location.reload();
120
+          });
121
+        });
122
+      } else {
123
+        // Service worker found. Proceed as normal.
124
+        registerValidSW(swUrl, config);
125
+      }
126
+    })
127
+    .catch(() => {
128
+      console.log('No internet connection found. App is running in offline mode.');
129
+    });
130
+}
131
+
132
+export function unregister() {
133
+  if ('serviceWorker' in navigator) {
134
+    navigator.serviceWorker.ready
135
+      .then((registration) => {
136
+        registration.unregister();
137
+      })
138
+      .catch((error) => {
139
+        console.error(error.message);
140
+      });
141
+  }
142
+}

+ 14
- 0
src/setupTests.ts View File

@@ -0,0 +1,14 @@
1
+// jest-dom adds custom jest matchers for asserting on DOM nodes.
2
+// allows you to do things like:
3
+// expect(element).toHaveTextContent(/react/i)
4
+// learn more: https://github.com/testing-library/jest-dom
5
+import '@testing-library/jest-dom/extend-expect';
6
+
7
+// Mock matchmedia
8
+window.matchMedia = window.matchMedia || function() {
9
+  return {
10
+      matches: false,
11
+      addListener: function() {},
12
+      removeListener: function() {}
13
+  };
14
+};

+ 236
- 0
src/theme/variables.css View File

@@ -0,0 +1,236 @@
1
+/* Ionic Variables and Theming. For more info, please see:
2
+http://ionicframework.com/docs/theming/ */
3
+
4
+/** Ionic CSS Variables **/
5
+:root {
6
+  /** primary **/
7
+  --ion-color-primary: #3880ff;
8
+  --ion-color-primary-rgb: 56, 128, 255;
9
+  --ion-color-primary-contrast: #ffffff;
10
+  --ion-color-primary-contrast-rgb: 255, 255, 255;
11
+  --ion-color-primary-shade: #3171e0;
12
+  --ion-color-primary-tint: #4c8dff;
13
+
14
+  /** secondary **/
15
+  --ion-color-secondary: #3dc2ff;
16
+  --ion-color-secondary-rgb: 61, 194, 255;
17
+  --ion-color-secondary-contrast: #ffffff;
18
+  --ion-color-secondary-contrast-rgb: 255, 255, 255;
19
+  --ion-color-secondary-shade: #36abe0;
20
+  --ion-color-secondary-tint: #50c8ff;
21
+
22
+  /** tertiary **/
23
+  --ion-color-tertiary: #5260ff;
24
+  --ion-color-tertiary-rgb: 82, 96, 255;
25
+  --ion-color-tertiary-contrast: #ffffff;
26
+  --ion-color-tertiary-contrast-rgb: 255, 255, 255;
27
+  --ion-color-tertiary-shade: #4854e0;
28
+  --ion-color-tertiary-tint: #6370ff;
29
+
30
+  /** success **/
31
+  --ion-color-success: #2dd36f;
32
+  --ion-color-success-rgb: 45, 211, 111;
33
+  --ion-color-success-contrast: #ffffff;
34
+  --ion-color-success-contrast-rgb: 255, 255, 255;
35
+  --ion-color-success-shade: #28ba62;
36
+  --ion-color-success-tint: #42d77d;
37
+
38
+  /** warning **/
39
+  --ion-color-warning: #ffc409;
40
+  --ion-color-warning-rgb: 255, 196, 9;
41
+  --ion-color-warning-contrast: #000000;
42
+  --ion-color-warning-contrast-rgb: 0, 0, 0;
43
+  --ion-color-warning-shade: #e0ac08;
44
+  --ion-color-warning-tint: #ffca22;
45
+
46
+  /** danger **/
47
+  --ion-color-danger: #eb445a;
48
+  --ion-color-danger-rgb: 235, 68, 90;
49
+  --ion-color-danger-contrast: #ffffff;
50
+  --ion-color-danger-contrast-rgb: 255, 255, 255;
51
+  --ion-color-danger-shade: #cf3c4f;
52
+  --ion-color-danger-tint: #ed576b;
53
+
54
+  /** dark **/
55
+  --ion-color-dark: #222428;
56
+  --ion-color-dark-rgb: 34, 36, 40;
57
+  --ion-color-dark-contrast: #ffffff;
58
+  --ion-color-dark-contrast-rgb: 255, 255, 255;
59
+  --ion-color-dark-shade: #1e2023;
60
+  --ion-color-dark-tint: #383a3e;
61
+
62
+  /** medium **/
63
+  --ion-color-medium: #92949c;
64
+  --ion-color-medium-rgb: 146, 148, 156;
65
+  --ion-color-medium-contrast: #ffffff;
66
+  --ion-color-medium-contrast-rgb: 255, 255, 255;
67
+  --ion-color-medium-shade: #808289;
68
+  --ion-color-medium-tint: #9d9fa6;
69
+
70
+  /** light **/
71
+  --ion-color-light: #f4f5f8;
72
+  --ion-color-light-rgb: 244, 245, 248;
73
+  --ion-color-light-contrast: #000000;
74
+  --ion-color-light-contrast-rgb: 0, 0, 0;
75
+  --ion-color-light-shade: #d7d8da;
76
+  --ion-color-light-tint: #f5f6f9;
77
+}
78
+
79
+@media (prefers-color-scheme: dark) {
80
+  /*
81
+   * Dark Colors
82
+   * -------------------------------------------
83
+   */
84
+
85
+  body {
86
+    --ion-color-primary: #428cff;
87
+    --ion-color-primary-rgb: 66,140,255;
88
+    --ion-color-primary-contrast: #ffffff;
89
+    --ion-color-primary-contrast-rgb: 255,255,255;
90
+    --ion-color-primary-shade: #3a7be0;
91
+    --ion-color-primary-tint: #5598ff;
92
+
93
+    --ion-color-secondary: #50c8ff;
94
+    --ion-color-secondary-rgb: 80,200,255;
95
+    --ion-color-secondary-contrast: #ffffff;
96
+    --ion-color-secondary-contrast-rgb: 255,255,255;
97
+    --ion-color-secondary-shade: #46b0e0;
98
+    --ion-color-secondary-tint: #62ceff;
99
+
100
+    --ion-color-tertiary: #6a64ff;
101
+    --ion-color-tertiary-rgb: 106,100,255;
102
+    --ion-color-tertiary-contrast: #ffffff;
103
+    --ion-color-tertiary-contrast-rgb: 255,255,255;
104
+    --ion-color-tertiary-shade: #5d58e0;
105
+    --ion-color-tertiary-tint: #7974ff;
106
+
107
+    --ion-color-success: #2fdf75;
108
+    --ion-color-success-rgb: 47,223,117;
109
+    --ion-color-success-contrast: #000000;
110
+    --ion-color-success-contrast-rgb: 0,0,0;
111
+    --ion-color-success-shade: #29c467;
112
+    --ion-color-success-tint: #44e283;
113
+
114
+    --ion-color-warning: #ffd534;
115
+    --ion-color-warning-rgb: 255,213,52;
116
+    --ion-color-warning-contrast: #000000;
117
+    --ion-color-warning-contrast-rgb: 0,0,0;
118
+    --ion-color-warning-shade: #e0bb2e;
119
+    --ion-color-warning-tint: #ffd948;
120
+
121
+    --ion-color-danger: #ff4961;
122
+    --ion-color-danger-rgb: 255,73,97;
123
+    --ion-color-danger-contrast: #ffffff;
124
+    --ion-color-danger-contrast-rgb: 255,255,255;
125
+    --ion-color-danger-shade: #e04055;
126
+    --ion-color-danger-tint: #ff5b71;
127
+
128
+    --ion-color-dark: #f4f5f8;
129
+    --ion-color-dark-rgb: 244,245,248;
130
+    --ion-color-dark-contrast: #000000;
131
+    --ion-color-dark-contrast-rgb: 0,0,0;
132
+    --ion-color-dark-shade: #d7d8da;
133
+    --ion-color-dark-tint: #f5f6f9;
134
+
135
+    --ion-color-medium: #989aa2;
136
+    --ion-color-medium-rgb: 152,154,162;
137
+    --ion-color-medium-contrast: #000000;
138
+    --ion-color-medium-contrast-rgb: 0,0,0;
139
+    --ion-color-medium-shade: #86888f;
140
+    --ion-color-medium-tint: #a2a4ab;
141
+
142
+    --ion-color-light: #222428;
143
+    --ion-color-light-rgb: 34,36,40;
144
+    --ion-color-light-contrast: #ffffff;
145
+    --ion-color-light-contrast-rgb: 255,255,255;
146
+    --ion-color-light-shade: #1e2023;
147
+    --ion-color-light-tint: #383a3e;
148
+  }
149
+
150
+  /*
151
+   * iOS Dark Theme
152
+   * -------------------------------------------
153
+   */
154
+
155
+  .ios body {
156
+    --ion-background-color: #000000;
157
+    --ion-background-color-rgb: 0,0,0;
158
+
159
+    --ion-text-color: #ffffff;
160
+    --ion-text-color-rgb: 255,255,255;
161
+
162
+    --ion-color-step-50: #0d0d0d;
163
+    --ion-color-step-100: #1a1a1a;
164
+    --ion-color-step-150: #262626;
165
+    --ion-color-step-200: #333333;
166
+    --ion-color-step-250: #404040;
167
+    --ion-color-step-300: #4d4d4d;
168
+    --ion-color-step-350: #595959;
169
+    --ion-color-step-400: #666666;
170
+    --ion-color-step-450: #737373;
171
+    --ion-color-step-500: #808080;
172
+    --ion-color-step-550: #8c8c8c;
173
+    --ion-color-step-600: #999999;
174
+    --ion-color-step-650: #a6a6a6;
175
+    --ion-color-step-700: #b3b3b3;
176
+    --ion-color-step-750: #bfbfbf;
177
+    --ion-color-step-800: #cccccc;
178
+    --ion-color-step-850: #d9d9d9;
179
+    --ion-color-step-900: #e6e6e6;
180
+    --ion-color-step-950: #f2f2f2;
181
+
182
+    --ion-item-background: #000000;
183
+
184
+    --ion-card-background: #1c1c1d;
185
+  }
186
+
187
+  .ios ion-modal {
188
+    --ion-background-color: var(--ion-color-step-100);
189
+    --ion-toolbar-background: var(--ion-color-step-150);
190
+    --ion-toolbar-border-color: var(--ion-color-step-250);
191
+  }
192
+
193
+
194
+  /*
195
+   * Material Design Dark Theme
196
+   * -------------------------------------------
197
+   */
198
+
199
+  .md body {
200
+    --ion-background-color: #121212;
201
+    --ion-background-color-rgb: 18,18,18;
202
+
203
+    --ion-text-color: #ffffff;
204
+    --ion-text-color-rgb: 255,255,255;
205
+
206
+    --ion-border-color: #222222;
207
+
208
+    --ion-color-step-50: #1e1e1e;
209
+    --ion-color-step-100: #2a2a2a;
210
+    --ion-color-step-150: #363636;
211
+    --ion-color-step-200: #414141;
212
+    --ion-color-step-250: #4d4d4d;
213
+    --ion-color-step-300: #595959;
214
+    --ion-color-step-350: #656565;
215
+    --ion-color-step-400: #717171;
216
+    --ion-color-step-450: #7d7d7d;
217
+    --ion-color-step-500: #898989;
218
+    --ion-color-step-550: #949494;
219
+    --ion-color-step-600: #a0a0a0;
220
+    --ion-color-step-650: #acacac;
221
+    --ion-color-step-700: #b8b8b8;
222
+    --ion-color-step-750: #c4c4c4;
223
+    --ion-color-step-800: #d0d0d0;
224
+    --ion-color-step-850: #dbdbdb;
225
+    --ion-color-step-900: #e7e7e7;
226
+    --ion-color-step-950: #f3f3f3;
227
+
228
+    --ion-item-background: #1e1e1e;
229
+
230
+    --ion-toolbar-background: #1f1f1f;
231
+
232
+    --ion-tab-bar-background: #1f1f1f;
233
+
234
+    --ion-card-background: #1e1e1e;
235
+  }
236
+}

+ 26
- 0
tsconfig.json View File

@@ -0,0 +1,26 @@
1
+{
2
+  "compilerOptions": {
3
+    "target": "es5",
4
+    "lib": [
5
+      "dom",
6
+      "dom.iterable",
7
+      "esnext"
8
+    ],
9
+    "allowJs": true,
10
+    "skipLibCheck": true,
11
+    "esModuleInterop": true,
12
+    "allowSyntheticDefaultImports": true,
13
+    "strict": true,
14
+    "forceConsistentCasingInFileNames": true,
15
+    "noFallthroughCasesInSwitch": true,
16
+    "module": "esnext",
17
+    "moduleResolution": "node",
18
+    "resolveJsonModule": true,
19
+    "isolatedModules": true,
20
+    "noEmit": true,
21
+    "jsx": "react-jsx"
22
+  },
23
+  "include": [
24
+    "src"
25
+  ]
26
+}