Browse Source

Merge branch 'matteing-sprint-1' of sergio.mattei/RenacerSocial into main

sergio.mattei 1 year ago
parent
commit
2213cd9df8

+ 5
- 5
capacitor.config.ts View File

@@ -1,10 +1,10 @@
1
-import { CapacitorConfig } from '@capacitor/cli';
1
+import { CapacitorConfig } from "@capacitor/cli";
2 2
 
3 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 10
 export default config;

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

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

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

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

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


+ 5
- 0
package.json View File

@@ -23,11 +23,15 @@
23 23
     "@types/react-router-dom": "^5.1.7",
24 24
     "history": "^4.9.0",
25 25
     "ionicons": "^6.0.3",
26
+    "lodash": "^4.17.21",
27
+    "notion-utils": "^6.15.6",
26 28
     "react": "^18.2.0",
27 29
     "react-dom": "^18.2.0",
30
+    "react-notion-x": "^6.15.7",
28 31
     "react-router": "^5.2.0",
29 32
     "react-router-dom": "^5.2.0",
30 33
     "react-scripts": "^5.0.0",
34
+    "swr": "^1.3.0",
31 35
     "typescript": "^4.1.3",
32 36
     "web-vitals": "^0.2.4",
33 37
     "workbox-background-sync": "^5.1.4",
@@ -71,6 +75,7 @@
71 75
   "devDependencies": {
72 76
     "@capacitor/cli": "4.4.0",
73 77
     "@ionic/lab": "3.2.15",
78
+    "@types/lodash": "^4.14.191",
74 79
     "husky": "^8.0.0",
75 80
     "pretty-quick": "^3.1.3"
76 81
   },

+ 3
- 1
src/App.tsx View File

@@ -34,6 +34,8 @@ import "./theme/variables.css";
34 34
 import AdviceListPage from "./pages/AdviceListPage";
35 35
 import LawListPage from "./pages/LawListPage";
36 36
 import ArticlePage from "./pages/ArticlePage";
37
+import "./theme/global.css";
38
+import "react-notion-x/src/styles.css";
37 39
 
38 40
 setupIonicReact();
39 41
 
@@ -51,7 +53,7 @@ const App: React.FC = () => (
51 53
           <Route path="/laws">
52 54
             <LawListPage />
53 55
           </Route>
54
-          <Route path="/article/:slug" component={ArticlePage} />
56
+          <Route path="/article/:articleId" component={ArticlePage} />
55 57
           <Route exact path="/">
56 58
             <Redirect to="/home" />
57 59
           </Route>

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

@@ -1,10 +1,13 @@
1
-
2 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 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,27 +0,0 @@
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

@@ -0,0 +1,12 @@
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

@@ -0,0 +1,13 @@
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
+}

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

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

+ 1
- 0
src/config.ts View File

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

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

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

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

@@ -3,38 +3,43 @@ import {
3 3
   IonHeader,
4 4
   IonPage,
5 5
   IonTitle,
6
+  IonToast,
6 7
   IonToolbar,
7 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 15
 const AdviceListPage: React.FC = () => {
16
+  const { data, error } = useSWR<Topic[]>(
17
+    "/api/getTopicList?type=advice",
18
+    fetcher
19
+  );
20
+  const isLoading = !data && !error;
21
+
23 22
   return (
24 23
     <IonPage>
25 24
       <IonHeader>
26 25
         <IonToolbar>
27
-          <IonTitle>Consejos</IonTitle>
26
+          <IonTitle>Advice</IonTitle>
28 27
         </IonToolbar>
29 28
       </IonHeader>
30 29
       <IonContent fullscreen>
31 30
         <IonHeader collapse="condense">
32 31
           <IonToolbar>
33
-            <IonTitle size="large">Consejos</IonTitle>
32
+            <IonTitle size="large">Advice</IonTitle>
34 33
           </IonToolbar>
35 34
         </IonHeader>
36
-
37
-        <DropdownComponent category={categories} />
35
+        {data && <TopicList loading={isLoading} topics={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
+        />
38 43
       </IonContent>
39 44
     </IonPage>
40 45
   );

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

@@ -13,10 +13,32 @@ import {
13 13
   IonToolbar,
14 14
 } from "@ionic/react";
15 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 omit from "lodash/omit";
21
+import SkeletonText from "../components/SkeletonText";
16 22
 
17
-const ArticlePage: React.FC<RouteComponentProps<{ slug: string }>> = ({
23
+const ArticlePage: React.FC<RouteComponentProps<{ articleId: string }>> = ({
18 24
   match,
19 25
 }) => {
26
+  const { data, error } = useSWR<any>(
27
+    `/api/getTopic?id=${match.params.articleId}`,
28
+    fetcher
29
+  );
30
+  const isLoading = !data && !error;
31
+  const keys = Object.keys(data?.block || {});
32
+  const block = data?.block?.[keys[0]]?.value;
33
+  const title = data && getBlockTitle(block, data);
34
+  const date =
35
+    data &&
36
+    formatDate(block?.last_edited_time, {
37
+      month: "long",
38
+    });
39
+  const recordMap = data;
40
+  console.log(recordMap);
41
+
20 42
   return (
21 43
     <IonPage>
22 44
       <IonHeader>
@@ -24,25 +46,35 @@ const ArticlePage: React.FC<RouteComponentProps<{ slug: string }>> = ({
24 46
           <IonButtons slot="start">
25 47
             <IonBackButton />
26 48
           </IonButtons>
27
-          <IonTitle>{match.params.slug}</IonTitle>
49
+          <IonTitle>
50
+            {data ? getBlockTitle(block, data) : <SkeletonText />}
51
+          </IonTitle>
28 52
         </IonToolbar>
29 53
       </IonHeader>
30 54
       <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>
55
+        <IonCardHeader className="notion">
56
+          <IonCardTitle>{title ?? <SkeletonText />}</IonCardTitle>
57
+          <IonCardSubtitle>{date ?? <SkeletonText />}</IonCardSubtitle>
58
+        </IonCardHeader>
59
+        <IonCardContent>
60
+          {!recordMap && (
61
+            <div>
62
+              <SkeletonText />
63
+              <SkeletonText />
64
+              <SkeletonText />
65
+              <SkeletonText />
66
+              <SkeletonText />
67
+              <SkeletonText />
68
+            </div>
69
+          )}
70
+          {recordMap && (
71
+            <NotionRenderer
72
+              recordMap={recordMap}
73
+              fullPage={true}
74
+              darkMode={false}
75
+            />
76
+          )}
77
+        </IonCardContent>
46 78
       </IonContent>
47 79
     </IonPage>
48 80
   );

+ 0
- 7
src/pages/Home.tsx View File

@@ -5,8 +5,6 @@ import {
5 5
   IonTitle,
6 6
   IonToolbar,
7 7
 } from "@ionic/react";
8
-import "../components/ListComponent";
9
-import ListComponent from "../components/ListComponent";
10 8
 
11 9
 const Home: React.FC = () => {
12 10
   return (
@@ -29,11 +27,6 @@ const Home: React.FC = () => {
29 27
             printing and typesetting industry. Lorem Ipsum.
30 28
           </p>
31 29
         </div>
32
-
33
-        <ListComponent
34
-          items={["Articulo 1", "Articulo 2"]}
35
-          title="Articulos Recientes"
36
-        />
37 30
       </IonContent>
38 31
     </IonPage>
39 32
   );

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

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

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

@@ -0,0 +1,19 @@
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
+}

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

@@ -76,12 +76,8 @@ http://ionicframework.com/docs/theming/ */
76 76
   --ion-color-light-tint: #f5f6f9;
77 77
 }
78 78
 
79
+/*
79 80
 @media (prefers-color-scheme: dark) {
80
-  /*
81
-   * Dark Colors
82
-   * -------------------------------------------
83
-   */
84
-
85 81
   body {
86 82
     --ion-color-primary: #428cff;
87 83
     --ion-color-primary-rgb: 66,140,255;
@@ -147,11 +143,6 @@ http://ionicframework.com/docs/theming/ */
147 143
     --ion-color-light-tint: #383a3e;
148 144
   }
149 145
 
150
-  /*
151
-   * iOS Dark Theme
152
-   * -------------------------------------------
153
-   */
154
-
155 146
   .ios body {
156 147
     --ion-background-color: #000000;
157 148
     --ion-background-color-rgb: 0,0,0;
@@ -190,12 +181,6 @@ http://ionicframework.com/docs/theming/ */
190 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 184
   .md body {
200 185
     --ion-background-color: #121212;
201 186
     --ion-background-color-rgb: 18,18,18;
@@ -234,3 +219,4 @@ http://ionicframework.com/docs/theming/ */
234 219
     --ion-card-background: #1e1e1e;
235 220
   }
236 221
 }
222
+*/

+ 8
- 4
src/types.ts View File

@@ -1,4 +1,8 @@
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
+}