Browse Source

Merge branch 'main' of https://git.ccom.uprrp.edu/sergio.mattei/RenacerSocial into eliam-sprint-1

# Conflicts:
#	src/components/DropdownComponent.tsx
#	src/components/ListComponent.tsx
#	src/pages/AdviceListPage.tsx
eliam.ruiz 1 year ago
parent
commit
13edd3582f

+ 15
- 0
.expo/README.md View File

1
+> Why do I have a folder named ".expo" in my project?
2
+
3
+The ".expo" folder is created when an Expo project is started using "expo start" command.
4
+
5
+> What do the files contain?
6
+
7
+- "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds.
8
+- "packager-info.json": contains port numbers and process PIDs that are used to serve the application to the mobile device/simulator.
9
+- "settings.json": contains the server configuration that is used to serve the application manifest.
10
+
11
+> Should I commit the ".expo" folder?
12
+
13
+No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine.
14
+
15
+Upon project creation, the ".expo" folder is already added to your ".gitignore" file.

+ 8
- 0
.expo/settings.json View File

1
+{
2
+  "hostType": "lan",
3
+  "lanType": "ip",
4
+  "dev": true,
5
+  "minify": false,
6
+  "urlRandomness": null,
7
+  "https": false
8
+}

+ 5
- 5
capacitor.config.ts View File

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

+ 10
- 0
cypress.config.ts View File

1
+import { defineConfig } from "cypress";
2
+
3
+export default defineConfig({
4
+  e2e: {
5
+    baseUrl: "http://localhost:3001",
6
+    setupNodeEvents(on, config) {
7
+      // implement node event listeners here
8
+    },
9
+  },
10
+});

+ 2
- 0
cypress/e2e/eduardo.cy.ts View File

1
+// I will E2E test the article page.
2
+// User visits home -> User clicks on "laws or advice" -> List renders with 200

+ 50
- 0
cypress/e2e/eliam.cy.ts View File

1
+// I will E2E test the article page.
2
+// User visits home -> User clicks on "laws or advice" -> Article page contains the title
3
+
4
+
5
+describe("Our app", () => {
6
+    beforeEach(() => {
7
+      cy.intercept(
8
+        { pathname: "/api/getTopicList" },
9
+        {
10
+          fixture: "topics",
11
+        }
12
+      ).as("getTopicList");
13
+  
14
+      cy.intercept(
15
+        { pathname: "/api/getTopic" },
16
+        {
17
+          fixture: "article",
18
+        }
19
+      ).as("getTopic");
20
+  
21
+      cy.viewport("iphone-x");
22
+      cy.visit("/");
23
+    });
24
+
25
+    it("shows the home page", () => {
26
+        cy.visit("/");
27
+        cy.contains("Bienvenidos");
28
+    });
29
+
30
+    it("clicks and loads the advice tab", () => {
31
+        cy.get("#tab-button-advice").first().click();
32
+        cy.wait("@getTopicList");
33
+    })
34
+    it("clicks and loads advice info", () => {
35
+        cy.get("#tab-button-advice").first().click();
36
+        cy.wait("@getTopicList");
37
+        cy.get(".item").first().click();
38
+        cy.wait("@getTopic");
39
+        cy.contains("Adopción");
40
+        cy.contains("Enmendado en el 1939");
41
+    })
42
+    it("clicks and loads law info", () => {
43
+        cy.get("#tab-button-laws").first().click();
44
+        cy.wait("@getTopicList");
45
+        cy.get(".item").first().click();
46
+        cy.wait("@getTopic");
47
+        cy.contains("Adopción");
48
+        cy.contains("Enmendado en el 1939");
49
+    })
50
+})

+ 56
- 0
cypress/e2e/sergio.cy.ts View File

1
+describe("Our app", () => {
2
+  beforeEach(() => {
3
+    cy.intercept(
4
+      { pathname: "/api/getTopicList" },
5
+      {
6
+        fixture: "topics",
7
+      }
8
+    ).as("getTopicList");
9
+
10
+    cy.intercept(
11
+      { pathname: "/api/getTopic" },
12
+      {
13
+        fixture: "article",
14
+      }
15
+    ).as("getTopic");
16
+
17
+    cy.viewport("iphone-x");
18
+    cy.visit("/");
19
+  });
20
+
21
+  it("shows the home page", () => {
22
+    cy.visit("/");
23
+    cy.contains("Bienvenidos");
24
+  });
25
+
26
+  it("shows a video in the home page", () => {
27
+    cy.visit("/");
28
+    cy.get("iframe").should("be.visible");
29
+  });
30
+
31
+  it("shows the about page", () => {
32
+    cy.visit("/about");
33
+    cy.contains("Renacer Social");
34
+  });
35
+
36
+  it("shows the individual service pages", () => {
37
+    cy.visit("/about");
38
+    cy.get(".item").first().click();
39
+    cy.contains("Casita de Paz");
40
+  });
41
+
42
+  it("shows the advice pages", () => {
43
+    cy.visit("/advice");
44
+    // Wait until the request to the intercept is completed.
45
+    cy.wait("@getTopicList");
46
+    cy.get(".item").first().click();
47
+    cy.wait("@getTopic");
48
+  });
49
+
50
+  it("allows exiting the individual service pages", () => {
51
+    cy.visit("/about");
52
+    cy.get(".item").first().click();
53
+    cy.get(".back-button-has-icon-only").first().click();
54
+    cy.contains("Servicios");
55
+  });
56
+});

+ 1643
- 0
cypress/fixtures/article.json
File diff suppressed because it is too large
View File


+ 14
- 0
cypress/fixtures/topics.json View File

1
+[
2
+  {
3
+    "blockId": "07ffbf26-babe-4c6a-b046-c7eb6d5c1a7f",
4
+    "name": "Pasa tiempo con tus hijos"
5
+  },
6
+  {
7
+    "blockId": "754d8ef3-14ae-4af6-a8c3-4ac4ede4d49d",
8
+    "name": "10 consejos para un matrimonio feliz"
9
+  },
10
+  {
11
+    "blockId": "d9fbaf7c-8f89-43b3-945c-927ed90e098b",
12
+    "name": "Busca ayuda si eres abusado/abusada"
13
+  }
14
+]

+ 37
- 0
cypress/support/commands.ts View File

1
+/// <reference types="cypress" />
2
+// ***********************************************
3
+// This example commands.ts shows you how to
4
+// create various custom commands and overwrite
5
+// existing commands.
6
+//
7
+// For more comprehensive examples of custom
8
+// commands please read more here:
9
+// https://on.cypress.io/custom-commands
10
+// ***********************************************
11
+//
12
+//
13
+// -- This is a parent command --
14
+// Cypress.Commands.add('login', (email, password) => { ... })
15
+//
16
+//
17
+// -- This is a child command --
18
+// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
19
+//
20
+//
21
+// -- This is a dual command --
22
+// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
23
+//
24
+//
25
+// -- This will overwrite an existing command --
26
+// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
27
+//
28
+// declare global {
29
+//   namespace Cypress {
30
+//     interface Chainable {
31
+//       login(email: string, password: string): Chainable<void>
32
+//       drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
33
+//       dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
34
+//       visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
35
+//     }
36
+//   }
37
+// }

+ 20
- 0
cypress/support/e2e.ts View File

1
+// ***********************************************************
2
+// This example support/e2e.ts is processed and
3
+// loaded automatically before your test files.
4
+//
5
+// This is a great place to put global configuration and
6
+// behavior that modifies Cypress.
7
+//
8
+// You can change the location of this file or turn off
9
+// automatically serving support files with the
10
+// 'supportFile' configuration option.
11
+//
12
+// You can read more here:
13
+// https://on.cypress.io/configuration
14
+// ***********************************************************
15
+
16
+// Import commands.js using ES2015 syntax:
17
+import "./commands";
18
+
19
+// Alternatively you can use CommonJS syntax:
20
+// require('./commands')

+ 6
- 2
ios/App/App.xcodeproj/project.pbxproj View File

347
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
347
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
348
 				CODE_SIGN_STYLE = Automatic;
348
 				CODE_SIGN_STYLE = Automatic;
349
 				CURRENT_PROJECT_VERSION = 1;
349
 				CURRENT_PROJECT_VERSION = 1;
350
+				DEVELOPMENT_TEAM = N9M43N6FY5;
350
 				INFOPLIST_FILE = App/Info.plist;
351
 				INFOPLIST_FILE = App/Info.plist;
352
+				INFOPLIST_KEY_CFBundleDisplayName = "Renacer Social";
351
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
353
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
352
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
354
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
353
 				MARKETING_VERSION = 1.0;
355
 				MARKETING_VERSION = 1.0;
354
 				OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
356
 				OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
355
-				PRODUCT_BUNDLE_IDENTIFIER = io.ionic.starter;
357
+				PRODUCT_BUNDLE_IDENTIFIER = io.renacer.social;
356
 				PRODUCT_NAME = "$(TARGET_NAME)";
358
 				PRODUCT_NAME = "$(TARGET_NAME)";
357
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
359
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
358
 				SWIFT_VERSION = 5.0;
360
 				SWIFT_VERSION = 5.0;
367
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
369
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
368
 				CODE_SIGN_STYLE = Automatic;
370
 				CODE_SIGN_STYLE = Automatic;
369
 				CURRENT_PROJECT_VERSION = 1;
371
 				CURRENT_PROJECT_VERSION = 1;
372
+				DEVELOPMENT_TEAM = N9M43N6FY5;
370
 				INFOPLIST_FILE = App/Info.plist;
373
 				INFOPLIST_FILE = App/Info.plist;
374
+				INFOPLIST_KEY_CFBundleDisplayName = "Renacer Social";
371
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
375
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
372
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
376
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
373
 				MARKETING_VERSION = 1.0;
377
 				MARKETING_VERSION = 1.0;
374
-				PRODUCT_BUNDLE_IDENTIFIER = io.ionic.starter;
378
+				PRODUCT_BUNDLE_IDENTIFIER = io.renacer.social;
375
 				PRODUCT_NAME = "$(TARGET_NAME)";
379
 				PRODUCT_NAME = "$(TARGET_NAME)";
376
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
380
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
377
 				SWIFT_VERSION = 5.0;
381
 				SWIFT_VERSION = 5.0;

+ 1
- 1
ios/App/App/Info.plist View File

5
 	<key>CFBundleDevelopmentRegion</key>
5
 	<key>CFBundleDevelopmentRegion</key>
6
 	<string>en</string>
6
 	<string>en</string>
7
 	<key>CFBundleDisplayName</key>
7
 	<key>CFBundleDisplayName</key>
8
-        <string>renacer</string>
8
+	<string>renacer</string>
9
 	<key>CFBundleExecutable</key>
9
 	<key>CFBundleExecutable</key>
10
 	<string>$(EXECUTABLE_NAME)</string>
10
 	<string>$(EXECUTABLE_NAME)</string>
11
 	<key>CFBundleIdentifier</key>
11
 	<key>CFBundleIdentifier</key>

+ 2859
- 30
package-lock.json
File diff suppressed because it is too large
View File


+ 9
- 2
package.json View File

23
     "@types/react-router-dom": "^5.1.7",
23
     "@types/react-router-dom": "^5.1.7",
24
     "history": "^4.9.0",
24
     "history": "^4.9.0",
25
     "ionicons": "^6.0.3",
25
     "ionicons": "^6.0.3",
26
+    "lodash": "^4.17.21",
27
+    "notion-utils": "^6.15.6",
26
     "react": "^18.2.0",
28
     "react": "^18.2.0",
27
     "react-dom": "^18.2.0",
29
     "react-dom": "^18.2.0",
30
+    "react-notion-x": "^6.15.7",
28
     "react-router": "^5.2.0",
31
     "react-router": "^5.2.0",
29
     "react-router-dom": "^5.2.0",
32
     "react-router-dom": "^5.2.0",
30
     "react-scripts": "^5.0.0",
33
     "react-scripts": "^5.0.0",
34
+    "swr": "^1.3.0",
31
     "typescript": "^4.1.3",
35
     "typescript": "^4.1.3",
32
     "web-vitals": "^0.2.4",
36
     "web-vitals": "^0.2.4",
33
     "workbox-background-sync": "^5.1.4",
37
     "workbox-background-sync": "^5.1.4",
44
     "workbox-streams": "^5.1.4"
48
     "workbox-streams": "^5.1.4"
45
   },
49
   },
46
   "scripts": {
50
   "scripts": {
47
-    "start": "react-scripts start",
51
+    "start": "PORT=3001 react-scripts start",
48
     "build": "react-scripts build",
52
     "build": "react-scripts build",
49
     "test": "react-scripts test --transformIgnorePatterns 'node_modules/(?!(@ionic/react|@ionic/react-router|@ionic/core|@stencil/core|ionicons)/)'",
53
     "test": "react-scripts test --transformIgnorePatterns 'node_modules/(?!(@ionic/react|@ionic/react-router|@ionic/core|@stencil/core|ionicons)/)'",
50
     "eject": "react-scripts eject",
54
     "eject": "react-scripts eject",
51
-    "prepare": "husky install"
55
+    "prepare": "husky install",
56
+    "cypress": "cypress open"
52
   },
57
   },
53
   "eslintConfig": {
58
   "eslintConfig": {
54
     "extends": [
59
     "extends": [
71
   "devDependencies": {
76
   "devDependencies": {
72
     "@capacitor/cli": "4.4.0",
77
     "@capacitor/cli": "4.4.0",
73
     "@ionic/lab": "3.2.15",
78
     "@ionic/lab": "3.2.15",
79
+    "@types/lodash": "^4.14.191",
80
+    "cypress": "^12.1.0",
74
     "husky": "^8.0.0",
81
     "husky": "^8.0.0",
75
     "pretty-quick": "^3.1.3"
82
     "pretty-quick": "^3.1.3"
76
   },
83
   },

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


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


BIN
public/assets/icon/law.jpeg View File


+ 49
- 4
src/App.tsx View File

10
   setupIonicReact,
10
   setupIonicReact,
11
 } from "@ionic/react";
11
 } from "@ionic/react";
12
 import { IonReactRouter } from "@ionic/react-router";
12
 import { IonReactRouter } from "@ionic/react-router";
13
-import { ellipse, home } from "ionicons/icons";
13
+import {
14
+  ellipse,
15
+  informationCircleSharp,
16
+  bookSharp,
17
+  appsSharp,
18
+  home,
19
+  peopleCircleSharp,
20
+} from "ionicons/icons";
14
 import Home from "./pages/Home";
21
 import Home from "./pages/Home";
15
 
22
 
16
 /* Core CSS required for Ionic components to work properly */
23
 /* Core CSS required for Ionic components to work properly */
34
 import AdviceListPage from "./pages/AdviceListPage";
41
 import AdviceListPage from "./pages/AdviceListPage";
35
 import LawListPage from "./pages/LawListPage";
42
 import LawListPage from "./pages/LawListPage";
36
 import ArticlePage from "./pages/ArticlePage";
43
 import ArticlePage from "./pages/ArticlePage";
44
+import "./theme/global.css";
45
+import "react-notion-x/src/styles.css";
46
+import AboutListPage from "./pages/AboutListPage";
47
+import CasitaPage from "./pages/services/CasitaPage";
48
+import CrecemosPage from "./pages/services/CrecemosPage";
49
+import LazosPage from "./pages/services/LazosPage";
50
+import SupervisadasPage from "./pages/services/SupervisadasPage";
37
 
51
 
38
 setupIonicReact();
52
 setupIonicReact();
39
 
53
 
45
           <Route exact path="/home">
59
           <Route exact path="/home">
46
             <Home />
60
             <Home />
47
           </Route>
61
           </Route>
62
+
48
           <Route path="/advice">
63
           <Route path="/advice">
49
             <AdviceListPage />
64
             <AdviceListPage />
50
           </Route>
65
           </Route>
66
+
51
           <Route path="/laws">
67
           <Route path="/laws">
52
             <LawListPage />
68
             <LawListPage />
53
           </Route>
69
           </Route>
54
-          <Route path="/article/:slug" component={ArticlePage} />
70
+
71
+          <Route path="/article/:articleId" component={ArticlePage} />
72
+
73
+          <Route path="/services/lazos">
74
+            <LazosPage />
75
+          </Route>
76
+
77
+          <Route path="/services/crecemos">
78
+            <CrecemosPage />
79
+          </Route>
80
+
81
+          <Route path="/services/supervisadas">
82
+            <SupervisadasPage />
83
+          </Route>
84
+
85
+          <Route path="/about">
86
+            <AboutListPage />
87
+          </Route>
88
+
89
+          <Route path="/services/casita">
90
+            <CasitaPage />
91
+          </Route>
92
+
55
           <Route exact path="/">
93
           <Route exact path="/">
56
             <Redirect to="/home" />
94
             <Redirect to="/home" />
57
           </Route>
95
           </Route>
58
         </IonRouterOutlet>
96
         </IonRouterOutlet>
97
+
59
         <IonTabBar slot="bottom">
98
         <IonTabBar slot="bottom">
60
           <IonTabButton tab="home" href="/home">
99
           <IonTabButton tab="home" href="/home">
61
             <IonIcon icon={home} />
100
             <IonIcon icon={home} />
62
             <IonLabel>Home</IonLabel>
101
             <IonLabel>Home</IonLabel>
63
           </IonTabButton>
102
           </IonTabButton>
103
+
64
           <IonTabButton tab="advice" href="/advice">
104
           <IonTabButton tab="advice" href="/advice">
65
-            <IonIcon icon={ellipse} />
105
+            <IonIcon icon={peopleCircleSharp} />
66
             <IonLabel>Advice</IonLabel>
106
             <IonLabel>Advice</IonLabel>
67
           </IonTabButton>
107
           </IonTabButton>
108
+
68
           <IonTabButton tab="laws" href="/laws">
109
           <IonTabButton tab="laws" href="/laws">
69
-            <IonIcon icon={ellipse} />
110
+            <IonIcon icon={bookSharp} />
70
             <IonLabel>Laws</IonLabel>
111
             <IonLabel>Laws</IonLabel>
71
           </IonTabButton>
112
           </IonTabButton>
113
+          <IonTabButton tab="about" href="/about">
114
+            <IonIcon icon={informationCircleSharp} />
115
+            <IonLabel>About</IonLabel>
116
+          </IonTabButton>
72
         </IonTabBar>
117
         </IonTabBar>
73
       </IonTabs>
118
       </IonTabs>
74
     </IonReactRouter>
119
     </IonReactRouter>

+ 0
- 3
src/components/DropdownComponent.css View File

1
-.accordion-list-container {
2
-    width: 100%;
3
-}

+ 0
- 34
src/components/DropdownComponent.tsx View File

1
-import {
2
-  IonAccordion,
3
-  IonAccordionGroup,
4
-  IonItem,
5
-  IonLabel,
6
-} from "@ionic/react";
7
-import ListComponent from "../components/ListComponent";
8
-import { Category } from "../types";
9
-import "./DropdownComponent.css";
10
-
11
-const DropdownComponent: React.FC<{
12
-  category: Category[];
13
-}> = (props) => {
14
-  const category = props.category;
15
-  return (
16
-    <IonAccordionGroup>
17
-      {category.map((catItem: Category, index) => {
18
-        return (
19
-          <IonAccordion value={`${index}`}>
20
-            <IonItem slot="header" color="light">
21
-              <IonLabel>{catItem.name}</IonLabel>
22
-            </IonItem>
23
-            <IonItem slot="content">
24
-              <div className="accordion-list-container">
25
-                <ListComponent items={catItem.listItems} />
26
-              </div>
27
-            </IonItem>
28
-          </IonAccordion>
29
-        );
30
-      })}
31
-    </IonAccordionGroup>
32
-  );
33
-};
34
-export default DropdownComponent;

+ 9
- 6
src/components/ListComponent.css View File

1
-
2
 .list {
1
 .list {
3
-    border-radius: 20px; 
4
-    padding: 0 0 0 0;
5
-    
2
+  border-radius: 20px;
3
+  padding: 0 0 0 0;
6
 }
4
 }
7
 
5
 
8
 .ListItemText {
6
 .ListItemText {
9
-    font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10
-}
7
+  font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
8
+  width: 100%;
9
+}
10
+
11
+ion-list-header {
12
+  /* color: "primary"; */
13
+}

+ 0
- 27
src/components/ListComponent.tsx View File

1
-import { IonItem, IonLabel, IonList, IonListHeader } from "@ionic/react";
2
-import "../components/ListComponent.css";
3
-
4
-const ListComponent: React.FC<{ title?: string; items: string[] }> = (
5
-  props
6
-) => {
7
-  const title = props.title;
8
-  const items = props.items;
9
-  return (
10
-    <IonList>
11
-      {title ? (
12
-        <IonListHeader>
13
-          <IonLabel>{title}</IonLabel>
14
-        </IonListHeader>
15
-      ) : null}
16
-
17
-      {items.map((itemName: string) => {
18
-        return (
19
-          <IonItem routerLink={`/article/${encodeURIComponent(itemName)}`}>
20
-            <p className="ListItemText">{itemName}</p>
21
-          </IonItem>
22
-        );
23
-      })}
24
-    </IonList>
25
-  );
26
-};
27
-export default ListComponent;

+ 12
- 0
src/components/SkeletonText.tsx View File

1
+import { IonSkeletonText } from "@ionic/react";
2
+import React from "react";
3
+
4
+function skeletonRange() {
5
+  return Math.floor(Math.random() * (75 - 25 + 1)) + 25;
6
+}
7
+
8
+export default function SkeletonText() {
9
+  return (
10
+    <IonSkeletonText animated={true} style={{ width: `${skeletonRange()}%` }} />
11
+  );
12
+}

+ 13
- 0
src/components/TopicList.css View File

1
+.list {
2
+  border-radius: 20px;
3
+  padding: 0 0 0 0;
4
+}
5
+
6
+.ListItemText {
7
+  font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
8
+  width: 100%;
9
+}
10
+
11
+ion-list-header {
12
+  /* color: "primary"; */
13
+}

+ 61
- 0
src/components/TopicList.tsx View File

1
+import {
2
+  IonContent,
3
+  IonItem,
4
+  IonLabel,
5
+  IonList,
6
+  IonListHeader,
7
+} from "@ionic/react";
8
+import { Topic } from "../types";
9
+import SkeletonText from "./SkeletonText";
10
+import "../components/TopicList.css";
11
+
12
+/**
13
+ * This component creates a shimmering "skeleton" version of the list component.
14
+ * It shows only when content isn't available yet (loading).
15
+ */
16
+function ListSkeleton({ items = 5 }: { items?: number }) {
17
+  return (
18
+    <>
19
+      {Array(items)
20
+        .fill(0)
21
+        .map((_, i) => (
22
+          <IonItem key={i}>
23
+            <SkeletonText />
24
+          </IonItem>
25
+        ))}
26
+    </>
27
+  );
28
+}
29
+
30
+const TopicList: React.FC<{
31
+  title?: string;
32
+  topics: Topic[];
33
+  loading?: boolean;
34
+}> = ({ title, topics, loading }) => {
35
+  console.log(topics);
36
+  return (
37
+    <IonList>
38
+      {title ? (
39
+        <IonListHeader>
40
+          <IonLabel>{title}</IonLabel>
41
+        </IonListHeader>
42
+      ) : null}
43
+
44
+      {loading && <ListSkeleton />}
45
+
46
+      {!loading &&
47
+        topics &&
48
+        topics.map((topic: Topic) => {
49
+          return (
50
+            <IonItem
51
+              key={topic.blockId}
52
+              routerLink={`/article/${topic.blockId}`}
53
+            >
54
+              <p className="ListItemText">{topic.name}</p>
55
+            </IonItem>
56
+          );
57
+        })}
58
+    </IonList>
59
+  );
60
+};
61
+export default TopicList;

+ 1
- 0
src/config.ts View File

1
+export const API_URL = process.env.API_URL ?? "http://localhost:3000";

+ 4
- 0
src/lib/api.ts View File

1
+import { API_URL } from "../config";
2
+
3
+export const fetcher = (url: string) =>
4
+  fetch(`${API_URL}${url}`).then((r) => r.json());

+ 69
- 0
src/pages/AboutListPage.tsx View File

1
+import {
2
+  IonContent,
3
+  IonHeader,
4
+  IonImg,
5
+  IonItem,
6
+  IonLabel,
7
+  IonList,
8
+  IonListHeader,
9
+  IonPage,
10
+  IonTitle,
11
+  IonToolbar,
12
+} from "@ionic/react";
13
+
14
+const AboutListPage: React.FC = () => {
15
+  return (
16
+    <IonPage>
17
+      <IonHeader>
18
+        <IonToolbar>
19
+          <IonTitle>About</IonTitle>
20
+        </IonToolbar>
21
+      </IonHeader>
22
+      <IonContent fullscreen>
23
+        <IonHeader collapse="condense">
24
+          <IonToolbar>
25
+            <IonTitle size="large">About</IonTitle>
26
+          </IonToolbar>
27
+        </IonHeader>
28
+        <IonContent className="ion-padding">
29
+          <div className="video">
30
+            <iframe
31
+              src="https://www.youtube.com/embed/MRo8FR2l6VM"
32
+              title="YouTube video player"
33
+              allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
34
+            ></iframe>
35
+          </div>
36
+          <h5>Bienvenidos</h5>
37
+          <p>
38
+            Renacer Social ha iniciado una campaña de orientación a la comunidad
39
+            para que los niños y niñas puedan denunciar su situación de
40
+            maltrato. Con una seña discreta que consta de una “X” formada frente
41
+            al torso con los dedos índice, el menor puede dar alerta de su
42
+            situación de maltrato. Es responsabilidad de todos denunciar la
43
+            posible situación de maltrato si vemos a un menor realizar dicha
44
+            seña.
45
+          </p>
46
+          <IonList>
47
+            <IonListHeader>
48
+              <IonLabel>Servicios</IonLabel>
49
+            </IonListHeader>
50
+            <IonItem routerLink={`/services/casita`}>
51
+              <p className="ListItemText">Casita de Paz</p>
52
+            </IonItem>
53
+            <IonItem routerLink={`/services/Crecemos`}>
54
+              <p className="ListItemText">Crecemos</p>
55
+            </IonItem>
56
+            <IonItem routerLink={`/services/Lazos`}>
57
+              <p className="ListItemText">Proyecto Lazos</p>
58
+            </IonItem>
59
+            <IonItem routerLink={`/services/Supervisadas`}>
60
+              <p className="ListItemText">Visitas Supervisadas</p>
61
+            </IonItem>
62
+          </IonList>
63
+        </IonContent>
64
+      </IonContent>
65
+    </IonPage>
66
+  );
67
+};
68
+
69
+export default AboutListPage;

+ 45
- 17
src/pages/AdviceListPage.tsx View File

3
   IonHeader,
3
   IonHeader,
4
   IonPage,
4
   IonPage,
5
   IonTitle,
5
   IonTitle,
6
+  IonToast,
6
   IonToolbar,
7
   IonToolbar,
7
 } from "@ionic/react";
8
 } from "@ionic/react";
8
-import DropdownComponent from "../components/DropdownComponent";
9
-import "../types";
10
-
11
-const categories = [
12
-  {
13
-    name: "Paternidad",
14
-    listItems: ["Consejo de Paternidad 1", "Consejo de Paternidad 2"],
15
-  },
16
-  {
17
-    name: "Abuso Sexual",
18
-    listItems: ["Consejo de Abuso Sexual 1", "Consejo de Abuso Sexual 2"],
19
-  },
20
-];
9
+import { alertCircleOutline } from "ionicons/icons";
10
+import useSWR from "swr";
11
+import TopicList from "../components/TopicList";
12
+import { fetcher } from "../lib/api";
13
+import { Topic } from "../types";
21
 
14
 
22
 const AdviceListPage: React.FC = () => {
15
 const AdviceListPage: React.FC = () => {
16
+  const { data: paternidad, error: paternidadError } = useSWR<Topic[]>(
17
+    "/api/getTopicList?type=advice&category=Paternidad",
18
+    fetcher
19
+  );
20
+  const { data: matrimonio, error: matrimonioError } = useSWR<Topic[]>(
21
+    "/api/getTopicList?type=advice&category=Matrimonio",
22
+    fetcher
23
+  );
24
+  const { data: abuso, error: abusoError } = useSWR<Topic[]>(
25
+    "/api/getTopicList?type=advice&category=Abuso%20Sexual",
26
+    fetcher
27
+  );
28
+  const error = paternidadError || matrimonioError || abusoError;
29
+
23
   return (
30
   return (
24
     <IonPage>
31
     <IonPage>
25
       <IonHeader>
32
       <IonHeader>
26
         <IonToolbar>
33
         <IonToolbar>
27
-          <IonTitle>Consejos</IonTitle>
34
+          <IonTitle>Advice</IonTitle>
28
         </IonToolbar>
35
         </IonToolbar>
29
       </IonHeader>
36
       </IonHeader>
30
       <IonContent fullscreen>
37
       <IonContent fullscreen>
31
         <IonHeader collapse="condense">
38
         <IonHeader collapse="condense">
32
           <IonToolbar>
39
           <IonToolbar>
33
-            <IonTitle size="large">Consejos</IonTitle>
40
+            <IonTitle size="large">Advice</IonTitle>
34
           </IonToolbar>
41
           </IonToolbar>
35
         </IonHeader>
42
         </IonHeader>
36
-        <DropdownComponent category={categories} />
43
+        <TopicList
44
+          title="Paternidad"
45
+          loading={!paternidad && !paternidadError}
46
+          topics={paternidad ? (paternidad as Topic[]) : []}
47
+        />
48
+        <TopicList
49
+          title="Matrimonio"
50
+          loading={!matrimonio && !matrimonioError}
51
+          topics={matrimonio ? (matrimonio as Topic[]) : []}
52
+        />
53
+        <TopicList
54
+          title="Abuso Sexual"
55
+          loading={!abuso && !abusoError}
56
+          topics={abuso ? (abuso as Topic[]) : []}
57
+        />
58
+        <IonToast
59
+          icon={alertCircleOutline}
60
+          color="danger"
61
+          isOpen={error !== undefined}
62
+          message="Failed to load items."
63
+          duration={1500}
64
+        />
37
       </IonContent>
65
       </IonContent>
38
     </IonPage>
66
     </IonPage>
39
   );
67
   );
40
 };
68
 };
41
 
69
 
42
-export default AdviceListPage;
70
+export default AdviceListPage;

+ 48
- 17
src/pages/ArticlePage.tsx View File

13
   IonToolbar,
13
   IonToolbar,
14
 } from "@ionic/react";
14
 } from "@ionic/react";
15
 import { RouteComponentProps } from "react-router";
15
 import { RouteComponentProps } from "react-router";
16
+import { NotionRenderer } from "react-notion-x";
17
+import { fetcher } from "../lib/api";
18
+import useSWR from "swr";
19
+import { formatDate, getBlockTitle, getPageProperty } from "notion-utils";
20
+import SkeletonText from "../components/SkeletonText";
16
 
21
 
17
-const ArticlePage: React.FC<RouteComponentProps<{ slug: string }>> = ({
22
+const ArticlePage: React.FC<RouteComponentProps<{ articleId: string }>> = ({
18
   match,
23
   match,
19
 }) => {
24
 }) => {
25
+  const { data, error } = useSWR<any>(
26
+    `/api/getTopic?id=${match.params.articleId}`,
27
+    fetcher
28
+  );
29
+  const isLoading = !data && !error;
30
+  const keys = Object.keys(data?.block || {});
31
+  const block = data?.block?.[keys[0]]?.value;
32
+  const title = data && getBlockTitle(block, data);
33
+  const date =
34
+    data &&
35
+    formatDate(block?.last_edited_time, {
36
+      month: "long",
37
+    });
38
+  const recordMap = data;
39
+  console.log(recordMap);
40
+
20
   return (
41
   return (
21
     <IonPage>
42
     <IonPage>
22
       <IonHeader>
43
       <IonHeader>
24
           <IonButtons slot="start">
45
           <IonButtons slot="start">
25
             <IonBackButton />
46
             <IonBackButton />
26
           </IonButtons>
47
           </IonButtons>
27
-          <IonTitle>{match.params.slug}</IonTitle>
48
+          <IonTitle>
49
+            {data ? getBlockTitle(block, data) : <SkeletonText />}
50
+          </IonTitle>
28
         </IonToolbar>
51
         </IonToolbar>
29
       </IonHeader>
52
       </IonHeader>
30
       <IonContent fullscreen>
53
       <IonContent fullscreen>
31
-        <IonCard>
32
-          <img
33
-            alt="Silhouette of mountains"
34
-            src="https://ionicframework.com/docs/img/demos/card-media.png"
35
-          />
36
-          <IonCardHeader>
37
-            <IonCardTitle>{match.params.slug}</IonCardTitle>
38
-            <IonCardSubtitle>Card Subtitle</IonCardSubtitle>
39
-          </IonCardHeader>
40
-
41
-          <IonCardContent>
42
-            Here's a small text description for the card content. Nothing more,
43
-            nothing less.
44
-          </IonCardContent>
45
-        </IonCard>
54
+        <IonCardHeader className="notion">
55
+          <IonCardTitle>{title ?? <SkeletonText />}</IonCardTitle>
56
+          <IonCardSubtitle>{date ?? <SkeletonText />}</IonCardSubtitle>
57
+        </IonCardHeader>
58
+        <IonCardContent>
59
+          {!recordMap && (
60
+            <div>
61
+              <SkeletonText />
62
+              <SkeletonText />
63
+              <SkeletonText />
64
+              <SkeletonText />
65
+              <SkeletonText />
66
+              <SkeletonText />
67
+            </div>
68
+          )}
69
+          {recordMap && (
70
+            <NotionRenderer
71
+              recordMap={recordMap}
72
+              fullPage={true}
73
+              darkMode={false}
74
+            />
75
+          )}
76
+        </IonCardContent>
46
       </IonContent>
77
       </IonContent>
47
     </IonPage>
78
     </IonPage>
48
   );
79
   );

+ 15
- 8
src/pages/Home.tsx View File

5
   IonTitle,
5
   IonTitle,
6
   IonToolbar,
6
   IonToolbar,
7
 } from "@ionic/react";
7
 } from "@ionic/react";
8
-import "../components/ListComponent";
9
-import ListComponent from "../components/ListComponent";
10
 
8
 
11
 const Home: React.FC = () => {
9
 const Home: React.FC = () => {
12
   return (
10
   return (
23
           </IonToolbar>
21
           </IonToolbar>
24
         </IonHeader>
22
         </IonHeader>
25
         <div className="ion-padding">
23
         <div className="ion-padding">
26
-          <h5>Bienvenidos</h5>
24
+          <div className="video">
25
+            <iframe
26
+              src="https://www.youtube.com/embed/xCtgekNpxI4"
27
+              title="YouTube video player"
28
+              allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
29
+            ></iframe>
30
+          </div>
31
+          <h2>Bienvenidos</h2>
27
           <p>
32
           <p>
28
             Somos una organización Lorem Ipsum is simply dummy text of the
33
             Somos una organización Lorem Ipsum is simply dummy text of the
29
             printing and typesetting industry. Lorem Ipsum.
34
             printing and typesetting industry. Lorem Ipsum.
30
           </p>
35
           </p>
31
-        </div>
32
 
36
 
33
-        <ListComponent
34
-          items={["Articulo 1", "Articulo 2"]}
35
-          title="Articulos Recientes"
36
-        />
37
+          <h3>Si se encuentra en peligro llame al 9-1-1 o al 787-343-2020</h3>
38
+          <p>
39
+            Si usted se encuentra en peligro inminente, llame a la Policía de
40
+            Puerto Rico al 9-1-1 o al 787-343-2020. Debe buscar protección para
41
+            usted y los suyos de manera inmediata. NO ESPERE.
42
+          </p>
43
+        </div>
37
       </IonContent>
44
       </IonContent>
38
     </IonPage>
45
     </IonPage>
39
   );
46
   );

+ 20
- 2
src/pages/LawListPage.tsx View File

3
   IonHeader,
3
   IonHeader,
4
   IonPage,
4
   IonPage,
5
   IonTitle,
5
   IonTitle,
6
+  IonToast,
6
   IonToolbar,
7
   IonToolbar,
7
 } from "@ionic/react";
8
 } from "@ionic/react";
8
-import ListComponent from "../components/ListComponent";
9
+import { alertCircleOutline } from "ionicons/icons";
10
+import useSWR from "swr";
11
+import TopicList from "../components/TopicList";
12
+import { fetcher } from "../lib/api";
13
+import { Topic } from "../types";
9
 
14
 
10
 const LawListPage: React.FC = () => {
15
 const LawListPage: React.FC = () => {
16
+  const { data, error } = useSWR<Topic[]>(
17
+    "/api/getTopicList?type=laws",
18
+    fetcher
19
+  );
20
+  const isLoading = !data && !error;
21
+
11
   return (
22
   return (
12
     <IonPage>
23
     <IonPage>
13
       <IonHeader>
24
       <IonHeader>
21
             <IonTitle size="large">Leyes</IonTitle>
32
             <IonTitle size="large">Leyes</IonTitle>
22
           </IonToolbar>
33
           </IonToolbar>
23
         </IonHeader>
34
         </IonHeader>
24
-        <ListComponent items={["Law 1", "Law 2"]} />
35
+        <TopicList loading={isLoading} topics={data ? (data as Topic[]) : []} />
36
+        <IonToast
37
+          icon={alertCircleOutline}
38
+          color="danger"
39
+          isOpen={error !== undefined}
40
+          message="Failed to load items."
41
+          duration={1500}
42
+        />
25
       </IonContent>
43
       </IonContent>
26
     </IonPage>
44
     </IonPage>
27
   );
45
   );

+ 46
- 0
src/pages/services/CasitaPage.tsx View File

1
+import {
2
+  IonBackButton,
3
+  IonButtons,
4
+  IonCard,
5
+  IonCardContent,
6
+  IonCardHeader,
7
+  IonCardSubtitle,
8
+  IonCardTitle,
9
+  IonContent,
10
+  IonHeader,
11
+  IonPage,
12
+  IonTitle,
13
+  IonToolbar,
14
+} from "@ionic/react";
15
+import { RouteComponentProps } from "react-router";
16
+
17
+const CasitaPage: React.FC = () => {
18
+  return (
19
+    <IonPage>
20
+      <IonHeader>
21
+        <IonToolbar>
22
+          <IonButtons slot="start">
23
+            <IonBackButton />
24
+          </IonButtons>
25
+          <IonTitle>Servicios</IonTitle>
26
+        </IonToolbar>
27
+      </IonHeader>
28
+      <IonContent fullscreen>
29
+        <IonCard>
30
+          <img
31
+            alt="Silhouette of mountains"
32
+            src="https://ionicframework.com/docs/img/demos/card-media.png"
33
+          />
34
+          <IonCardHeader>
35
+            <IonCardTitle>Casita de Paz</IonCardTitle>
36
+            <IonCardSubtitle></IonCardSubtitle>
37
+          </IonCardHeader>
38
+
39
+          <IonCardContent>Romper ciclos negativos</IonCardContent>
40
+        </IonCard>
41
+      </IonContent>
42
+    </IonPage>
43
+  );
44
+};
45
+
46
+export default CasitaPage;

+ 46
- 0
src/pages/services/CrecemosPage.tsx View File

1
+import {
2
+  IonBackButton,
3
+  IonButtons,
4
+  IonCard,
5
+  IonCardContent,
6
+  IonCardHeader,
7
+  IonCardSubtitle,
8
+  IonCardTitle,
9
+  IonContent,
10
+  IonHeader,
11
+  IonPage,
12
+  IonTitle,
13
+  IonToolbar,
14
+} from "@ionic/react";
15
+import { RouteComponentProps } from "react-router";
16
+
17
+const CrecemosPage: React.FC = () => {
18
+  return (
19
+    <IonPage>
20
+      <IonHeader>
21
+        <IonToolbar>
22
+          <IonButtons slot="start">
23
+            <IonBackButton />
24
+          </IonButtons>
25
+          <IonTitle>Servicios</IonTitle>
26
+        </IonToolbar>
27
+      </IonHeader>
28
+      <IonContent fullscreen>
29
+        <IonCard>
30
+          <img
31
+            alt="Silhouette of mountains"
32
+            src="https://ionicframework.com/docs/img/demos/card-media.png"
33
+          />
34
+          <IonCardHeader>
35
+            <IonCardTitle>Crecemos</IonCardTitle>
36
+            <IonCardSubtitle></IonCardSubtitle>
37
+          </IonCardHeader>
38
+
39
+          <IonCardContent>Educacion Co-parental</IonCardContent>
40
+        </IonCard>
41
+      </IonContent>
42
+    </IonPage>
43
+  );
44
+};
45
+
46
+export default CrecemosPage;

+ 46
- 0
src/pages/services/LazosPage.tsx View File

1
+import {
2
+  IonBackButton,
3
+  IonButtons,
4
+  IonCard,
5
+  IonCardContent,
6
+  IonCardHeader,
7
+  IonCardSubtitle,
8
+  IonCardTitle,
9
+  IonContent,
10
+  IonHeader,
11
+  IonPage,
12
+  IonTitle,
13
+  IonToolbar,
14
+} from "@ionic/react";
15
+import { RouteComponentProps } from "react-router";
16
+
17
+const LazosPage: React.FC = () => {
18
+  return (
19
+    <IonPage>
20
+      <IonHeader>
21
+        <IonToolbar>
22
+          <IonButtons slot="start">
23
+            <IonBackButton />
24
+          </IonButtons>
25
+          <IonTitle>Servicios</IonTitle>
26
+        </IonToolbar>
27
+      </IonHeader>
28
+      <IonContent fullscreen>
29
+        <IonCard>
30
+          <img
31
+            alt="Silhouette of mountains"
32
+            src="https://ionicframework.com/docs/img/demos/card-media.png"
33
+          />
34
+          <IonCardHeader>
35
+            <IonCardTitle>Proyecto de Lazos</IonCardTitle>
36
+            <IonCardSubtitle></IonCardSubtitle>
37
+          </IonCardHeader>
38
+
39
+          <IonCardContent>Maltrato de Parejas</IonCardContent>
40
+        </IonCard>
41
+      </IonContent>
42
+    </IonPage>
43
+  );
44
+};
45
+
46
+export default LazosPage;

+ 48
- 0
src/pages/services/SupervisadasPage.tsx View File

1
+import {
2
+  IonBackButton,
3
+  IonButtons,
4
+  IonCard,
5
+  IonCardContent,
6
+  IonCardHeader,
7
+  IonCardSubtitle,
8
+  IonCardTitle,
9
+  IonContent,
10
+  IonHeader,
11
+  IonPage,
12
+  IonTitle,
13
+  IonToolbar,
14
+} from "@ionic/react";
15
+import { RouteComponentProps } from "react-router";
16
+
17
+const SupervisadasPage: React.FC = () => {
18
+  return (
19
+    <IonPage>
20
+      <IonHeader>
21
+        <IonToolbar>
22
+          <IonButtons slot="start">
23
+            <IonBackButton />
24
+          </IonButtons>
25
+          <IonTitle>Servicios</IonTitle>
26
+        </IonToolbar>
27
+      </IonHeader>
28
+      <IonContent fullscreen>
29
+        <IonCard>
30
+          <img
31
+            alt="Silhouette of mountains"
32
+            src="https://ionicframework.com/docs/img/demos/card-media.png"
33
+          />
34
+          <IonCardHeader>
35
+            <IonCardTitle>Visitas Supervisadas</IonCardTitle>
36
+            <IonCardSubtitle></IonCardSubtitle>
37
+          </IonCardHeader>
38
+
39
+          <IonCardContent>
40
+            Espacio tranquilo y seguro para visitas supervisadas
41
+          </IonCardContent>
42
+        </IonCard>
43
+      </IonContent>
44
+    </IonPage>
45
+  );
46
+};
47
+
48
+export default SupervisadasPage;

+ 37
- 0
src/theme/global.css View File

1
+.notion-nav-header,
2
+.notion-title,
3
+.notion-header {
4
+  display: none !important;
5
+}
6
+
7
+.notion-page {
8
+  width: 100% !important;
9
+  padding: 0px 0px 0px 0px !important;
10
+}
11
+
12
+main.notion-page-no-cover {
13
+  padding-top: 0px !important;
14
+  margin-top: 0px !important;
15
+}
16
+
17
+.notion-blank {
18
+  display: none !important;
19
+}
20
+
21
+.video {
22
+  position: relative;
23
+  overflow: hidden;
24
+  width: 100%;
25
+  padding-top: 56.25%; /* 16:9 Aspect Ratio (divide 9 by 16 = 0.5625) */
26
+}
27
+
28
+/* Then style the iframe to fit in the container div with full height and width */
29
+.video iframe {
30
+  position: absolute;
31
+  top: 0;
32
+  left: 0;
33
+  bottom: 0;
34
+  right: 0;
35
+  width: 100%;
36
+  height: 100%;
37
+}

+ 2
- 16
src/theme/variables.css View File

76
   --ion-color-light-tint: #f5f6f9;
76
   --ion-color-light-tint: #f5f6f9;
77
 }
77
 }
78
 
78
 
79
+/*
79
 @media (prefers-color-scheme: dark) {
80
 @media (prefers-color-scheme: dark) {
80
-  /*
81
-   * Dark Colors
82
-   * -------------------------------------------
83
-   */
84
-
85
   body {
81
   body {
86
     --ion-color-primary: #428cff;
82
     --ion-color-primary: #428cff;
87
     --ion-color-primary-rgb: 66,140,255;
83
     --ion-color-primary-rgb: 66,140,255;
147
     --ion-color-light-tint: #383a3e;
143
     --ion-color-light-tint: #383a3e;
148
   }
144
   }
149
 
145
 
150
-  /*
151
-   * iOS Dark Theme
152
-   * -------------------------------------------
153
-   */
154
-
155
   .ios body {
146
   .ios body {
156
     --ion-background-color: #000000;
147
     --ion-background-color: #000000;
157
     --ion-background-color-rgb: 0,0,0;
148
     --ion-background-color-rgb: 0,0,0;
190
     --ion-toolbar-border-color: var(--ion-color-step-250);
181
     --ion-toolbar-border-color: var(--ion-color-step-250);
191
   }
182
   }
192
 
183
 
193
-
194
-  /*
195
-   * Material Design Dark Theme
196
-   * -------------------------------------------
197
-   */
198
-
199
   .md body {
184
   .md body {
200
     --ion-background-color: #121212;
185
     --ion-background-color: #121212;
201
     --ion-background-color-rgb: 18,18,18;
186
     --ion-background-color-rgb: 18,18,18;
234
     --ion-card-background: #1e1e1e;
219
     --ion-card-background: #1e1e1e;
235
   }
220
   }
236
 }
221
 }
222
+*/

+ 8
- 4
src/types.ts View File

1
-export interface Category {
2
-    name: string;
3
-    listItems: string[];
4
-}
1
+export interface Topic {
2
+  blockId: string;
3
+  name: string;
4
+}
5
+
6
+export interface Error {
7
+  status: string;
8
+}