Quellcode durchsuchen

Merge branch 'UserStory1' of git.ccom.uprrp.edu:CCOM4030/AlAlRy into UserStory2

Alejandro Soledad vor 1 Jahr
Ursprung
Commit
229914a258

+ 29
- 0
.gitignore Datei anzeigen

@@ -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 Datei anzeigen

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

+ 10
- 0
capacitor.config.ts Datei anzeigen

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

+ 7
- 0
ionic.config.json Datei anzeigen

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

+ 16150
- 0
package-lock.json
Datei-Diff unterdrückt, da er zu groß ist
Datei anzeigen


+ 72
- 0
package.json Datei anzeigen

@@ -0,0 +1,72 @@
1
+{
2
+  "name": "ionic-enciclopedia-pr",
3
+  "version": "0.0.1",
4
+  "private": true,
5
+  "dependencies": {
6
+    "@capacitor/app": "4.1.1",
7
+    "@capacitor/core": "4.5.0",
8
+    "@capacitor/haptics": "4.1.0",
9
+    "@capacitor/keyboard": "4.1.0",
10
+    "@capacitor/status-bar": "4.1.0",
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.5.0"
70
+  },
71
+  "description": "An Ionic project"
72
+}

BIN
public/assets/icon/favicon.png Datei anzeigen


BIN
public/assets/icon/icon.png Datei anzeigen


+ 1
- 0
public/assets/shapes.svg Datei anzeigen

@@ -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 Datei anzeigen

@@ -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 Datei anzeigen

@@ -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
+}

BIN
resources/icon.png Datei anzeigen


BIN
resources/splash.png Datei anzeigen


+ 8
- 0
src/App.test.tsx Datei anzeigen

@@ -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
+});

+ 29
- 0
src/App.tsx Datei anzeigen

@@ -0,0 +1,29 @@
1
+import { IonNav, setupIonicReact } from '@ionic/react';
2
+import HomePage from './pages/HomePage';
3
+
4
+/* Core CSS required for Ionic components to work properly */
5
+import '@ionic/react/css/core.css';
6
+
7
+/* Basic CSS for apps built with Ionic */
8
+import '@ionic/react/css/normalize.css';
9
+import '@ionic/react/css/structure.css';
10
+import '@ionic/react/css/typography.css';
11
+
12
+/* Optional CSS utils that can be commented out */
13
+import '@ionic/react/css/padding.css';
14
+import '@ionic/react/css/float-elements.css';
15
+import '@ionic/react/css/text-alignment.css';
16
+import '@ionic/react/css/text-transformation.css';
17
+import '@ionic/react/css/flex-utils.css';
18
+import '@ionic/react/css/display.css';
19
+
20
+/* Theme variables */
21
+import './theme/variables.css';
22
+
23
+setupIonicReact();
24
+
25
+const App: React.FC = () => (
26
+  <IonNav root={() => <HomePage />}></IonNav>
27
+);
28
+
29
+export default App;

BIN
src/assets/EnciclopediaPR.png Datei anzeigen


BIN
src/assets/RobertoClemente.jpg Datei anzeigen


+ 24
- 0
src/components/ExploreContainer.css Datei anzeigen

@@ -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 Datei anzeigen

@@ -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 Datei anzeigen

@@ -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/AboutUs.css Datei anzeigen


+ 46
- 0
src/pages/AboutUs.tsx Datei anzeigen

@@ -0,0 +1,46 @@
1
+import {
2
+  IonImg,
3
+  IonContent,
4
+  IonHeader,
5
+  IonTitle,
6
+  IonPage,
7
+  IonToolbar,
8
+  IonFooter,
9
+  IonNavLink,
10
+  IonTabButton,
11
+  IonIcon
12
+} from '@ionic/react';
13
+import { arrowBack } from 'ionicons/icons';
14
+import React from 'react';
15
+import './AboutUs.css';
16
+import HomePage from './HomePage';
17
+import EnciclopediaPR from '../assets/EnciclopediaPR.png';
18
+
19
+const AboutUs: React.FC = () => {
20
+  return (
21
+    <IonPage>
22
+      <IonHeader>
23
+        <IonToolbar>
24
+          <IonTitle>More About Us</IonTitle>
25
+          <IonImg style={{ height: 50, width: 100 }} src={EnciclopediaPR} alt='Logo'></IonImg>
26
+        </IonToolbar>
27
+      </IonHeader>
28
+      <IonContent fullscreen className="ion-padding">
29
+        <h1>Enciclopedia PR</h1>
30
+        <h2>More about Us:</h2>
31
+        <p>Para mas informacion de nosotros si les interensan ir a la pagina https://enciclopediapr.org/</p>
32
+      </IonContent>
33
+      <IonFooter>
34
+        <IonToolbar>
35
+          <IonNavLink routerDirection="forward" component={() => <HomePage />}>
36
+            <IonTabButton tab="HomePage" href="/HomePage">
37
+              <IonIcon icon={arrowBack} />
38
+            </IonTabButton>
39
+          </IonNavLink>
40
+        </IonToolbar>
41
+      </IonFooter>
42
+    </IonPage>
43
+  );
44
+};
45
+
46
+export default AboutUs;

+ 0
- 0
src/pages/Biography.css Datei anzeigen


+ 50
- 0
src/pages/Biography.tsx Datei anzeigen

@@ -0,0 +1,50 @@
1
+import {
2
+    IonPage,
3
+    IonContent,
4
+    IonHeader,
5
+    IonTitle,
6
+    IonToolbar,
7
+    IonFooter,
8
+    IonNavLink,
9
+    IonTabButton,
10
+    IonIcon,
11
+    IonImg
12
+} from '@ionic/react';
13
+import { arrowBack } from 'ionicons/icons';
14
+import React from 'react';
15
+import './Biography.css';
16
+import ListBiographies from './ListBiographies';
17
+import EnciclopediaPR from '../assets/EnciclopediaPR.png';
18
+import RobertoClemente from '../assets/RobertoClemente.jpg';
19
+
20
+const Biography: React.FC = () => {
21
+    return (
22
+        <IonPage>
23
+            <IonHeader>
24
+                <IonToolbar>
25
+                    <IonTitle>Biography Information</IonTitle>
26
+                    <IonImg style={{ height: 50, width: 100 }} src={EnciclopediaPR} alt='Logo'></IonImg>
27
+                </IonToolbar>
28
+            </IonHeader>
29
+            <IonContent fullscreen className="ion-padding">
30
+                <IonImg style={{ height: 300, width: 600 }} src={RobertoClemente} alt='Logo'></IonImg>
31
+                <h1>Roberto Clemente</h1>
32
+
33
+                <p>Pelotero, Filántropo</p>
34
+                <p>Fue un afamado pelotero puertorriqueño que formó parte de las Grandes Ligas de Estados Unidos. El notable jardinero derecho y bateador fue el primer latinoamericano en formar parte del Salón de la Fama del Béisbol de Estados Unidos. También fue conocido por su labor filantrópica.</p>
35
+                <p>Nació el 18 de agosto de 1934 en San Antón, Carolina. Sus padres fueron Melchor Clemente y Luisa Walker. Desde los ocho años mostró habilidades deportivas con su participación en programas de pequeñas ligas y atletismo infantil. Se destacó en pista y campo, deporte en el que ganó varias medallas, específicamente en tiro de jabalina y distancias cortas. Con apenas 14 años, ya formaba parte del equipo de softball de Sello Rojo, compañía procesadora de arroz. Más adelante, formó parte del equipo de pelota aficionado de Juncos. En 1952, con 18 años de edad, ingresó a la liga de beisbol profesional de Puerto Rico, cuando fue contratado por los Cangrejeros de Santurce. El tiempo que formó parte de este equipo fue crucial para su carrera, ya que pudo pulir sus destrezas deportivas.</p>
36
+            </IonContent>
37
+            <IonFooter>
38
+                <IonToolbar>
39
+                    <IonNavLink routerDirection="forward" component={() => <ListBiographies />}>
40
+                        <IonTabButton tab="ListBiographies" href="/ListBiographies">
41
+                            <IonIcon icon={arrowBack} />
42
+                        </IonTabButton>
43
+                    </IonNavLink>
44
+                </IonToolbar>
45
+            </IonFooter>
46
+        </IonPage>
47
+    );
48
+};
49
+
50
+export default Biography;

+ 4
- 0
src/pages/HomePage.css Datei anzeigen

@@ -0,0 +1,4 @@
1
+ion-card-header.ios {
2
+    display: flex;
3
+    flex-flow: column-reverse;
4
+}

+ 68
- 0
src/pages/HomePage.tsx Datei anzeigen

@@ -0,0 +1,68 @@
1
+import {
2
+  IonContent,
3
+  IonButton,
4
+  IonHeader,
5
+  IonPage,
6
+  IonTitle,
7
+  IonToolbar,
8
+  IonCard,
9
+  IonCardHeader,
10
+  IonCardSubtitle,
11
+  IonCardTitle,
12
+  IonNavLink,
13
+  IonFooter,
14
+  IonTabButton,
15
+  IonIcon,
16
+  IonLabel,
17
+  IonImg
18
+} from '@ionic/react';
19
+import { square } from 'ionicons/icons';
20
+import './HomePage.css';
21
+import ListBiographies from './ListBiographies';
22
+import AboutUs from './AboutUs';
23
+import EnciclopediaPR from '../assets/EnciclopediaPR.png';
24
+
25
+const HomePage: React.FC = () => {
26
+  return (
27
+    <IonPage>
28
+      <IonHeader>
29
+        <IonToolbar>
30
+          <IonTitle>Home Page</IonTitle>
31
+          <IonImg style={{ height: 50, width: 100 }} src={EnciclopediaPR} alt='Logo'></IonImg>
32
+        </IonToolbar>
33
+      </IonHeader>
34
+      <IonContent fullscreen>
35
+        <IonCard>
36
+          <IonCardHeader>
37
+            <IonCardTitle>Biography</IonCardTitle>
38
+            <IonCardSubtitle>Search for all Biographies</IonCardSubtitle>
39
+          </IonCardHeader>
40
+          <IonNavLink routerDirection="forward" component={() => <ListBiographies />}>
41
+            <IonButton size="default" expand="block" shape="round">
42
+              Go to
43
+            </IonButton>
44
+          </IonNavLink>
45
+        </IonCard>
46
+        <IonCard>
47
+          <IonCardHeader>
48
+            <IonCardTitle>Capsule</IonCardTitle>
49
+            <IonCardSubtitle>Search for all Capsule</IonCardSubtitle>
50
+          </IonCardHeader>
51
+          <IonButton size="default" expand="block" shape="round">Go to</IonButton>
52
+        </IonCard>
53
+      </IonContent>
54
+      <IonFooter>
55
+        <IonToolbar>
56
+          <IonNavLink routerDirection="forward" component={() => <AboutUs />}>
57
+            <IonTabButton tab="AboutUs" href="/AboutUs">
58
+              <IonIcon icon={square} />
59
+              <IonLabel>About Us</IonLabel>
60
+            </IonTabButton>
61
+          </IonNavLink>
62
+        </IonToolbar>
63
+      </IonFooter>
64
+    </IonPage>
65
+  );
66
+};
67
+
68
+export default HomePage;

+ 0
- 0
src/pages/ListBiographies.css Datei anzeigen


+ 67
- 0
src/pages/ListBiographies.tsx Datei anzeigen

@@ -0,0 +1,67 @@
1
+import {
2
+  IonImg,
3
+  IonCard,
4
+  IonCardContent,
5
+  IonCardHeader,
6
+  IonCardSubtitle,
7
+  IonCardTitle,
8
+  IonSearchbar,
9
+  IonContent,
10
+  IonHeader,
11
+  IonPage,
12
+  IonTitle,
13
+  IonToolbar,
14
+  IonButton,
15
+  IonNavLink,
16
+  IonFooter,
17
+  IonTabButton,
18
+  IonIcon
19
+} from '@ionic/react';
20
+import { arrowBack } from 'ionicons/icons';
21
+import './ListBiographies.css';
22
+import Biography from './Biography';
23
+import HomePage from './HomePage';
24
+import EnciclopediaPR from '../assets/EnciclopediaPR.png';
25
+import RobertoClemente from '../assets/RobertoClemente.jpg';
26
+
27
+const ListBiographies: React.FC = () => {
28
+  return (
29
+    <IonPage>
30
+      <IonHeader>
31
+        <IonToolbar>
32
+          <IonTitle>List of Biographies</IonTitle>
33
+          <IonImg style={{ height: 50, width: 100 }} src={EnciclopediaPR} alt='Logo'></IonImg>
34
+        </IonToolbar>
35
+      </IonHeader>
36
+      <IonContent fullscreen>
37
+        <IonSearchbar></IonSearchbar>
38
+        <IonCard>
39
+          <IonImg style={{ height: 300, width: 600 }} src={RobertoClemente} alt='Logo'></IonImg>
40
+          <IonCardHeader>
41
+            <IonCardTitle>Roberto Clemente</IonCardTitle>
42
+            <IonCardSubtitle>Tabs: Deportista</IonCardSubtitle>
43
+          </IonCardHeader>
44
+
45
+          <IonCardContent>
46
+            Fue un afamado pelotero puertorriqueño que formó parte de las Grandes Ligas de Estados Unidos. El notable jardinero derecho y bateador fue el primer latinoamericano en formar parte del Salón de la Fama del Béisbol de Estados Unidos. También fue conocido por su labor filantrópica.
47
+          </IonCardContent>
48
+
49
+          <IonNavLink routerDirection="forward" component={() => <Biography />}>
50
+            <IonButton>Go to</IonButton>
51
+          </IonNavLink>
52
+        </IonCard>
53
+      </IonContent>
54
+      <IonFooter>
55
+        <IonToolbar>
56
+          <IonNavLink routerDirection="forward" component={() => <HomePage />}>
57
+            <IonTabButton tab="HomePage" href="/HomePage">
58
+              <IonIcon icon={arrowBack} />
59
+            </IonTabButton>
60
+          </IonNavLink>
61
+        </IonToolbar>
62
+      </IonFooter>
63
+    </IonPage>
64
+  );
65
+};
66
+
67
+export default ListBiographies;

+ 1
- 0
src/react-app-env.d.ts Datei anzeigen

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

+ 15
- 0
src/reportWebVitals.ts Datei anzeigen

@@ -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 Datei anzeigen

@@ -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 Datei anzeigen

@@ -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 Datei anzeigen

@@ -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 Datei anzeigen

@@ -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: #afccff;
87
+    --ion-color-primary-rgb: 175, 204, 255;
88
+    --ion-color-primary-contrast: #000000;
89
+    --ion-color-primary-contrast-rgb: 0, 0, 0;
90
+    --ion-color-primary-shade: #9ab4e0;
91
+    --ion-color-primary-tint: #b7d1ff;
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 Datei anzeigen

@@ -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
+}