Browse Source

[feat] Add routing for Notion articles

Sergio Mattei 1 year ago
parent
commit
9fda4b116b

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

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


+ 4
- 0
package.json View File

@@ -23,8 +23,11 @@
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",
@@ -72,6 +75,7 @@
72 75
   "devDependencies": {
73 76
     "@capacitor/cli": "4.4.0",
74 77
     "@ionic/lab": "3.2.15",
78
+    "@types/lodash": "^4.14.191",
75 79
     "husky": "^8.0.0",
76 80
     "pretty-quick": "^3.1.3"
77 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>

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

src/components/ListComponent.tsx → src/components/TopicList.tsx View File

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

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

@@ -3,25 +3,43 @@ import {
3 3
   IonHeader,
4 4
   IonPage,
5 5
   IonTitle,
6
+  IonToast,
6 7
   IonToolbar,
7 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 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
+
11 22
   return (
12 23
     <IonPage>
13 24
       <IonHeader>
14 25
         <IonToolbar>
15
-          <IonTitle>Consejos</IonTitle>
26
+          <IonTitle>Advice</IonTitle>
16 27
         </IonToolbar>
17 28
       </IonHeader>
18 29
       <IonContent fullscreen>
19 30
         <IonHeader collapse="condense">
20 31
           <IonToolbar>
21
-            <IonTitle size="large">Consejos</IonTitle>
32
+            <IonTitle size="large">Advice</IonTitle>
22 33
           </IonToolbar>
23 34
         </IonHeader>
24
-        <ListComponent laws={["Consejo 1", "Consejo 2"]} />
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
+        />
25 43
       </IonContent>
26 44
     </IonPage>
27 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
-          laws={["Articulo 1", "Articulo 2"]}
35
-          title="Articulos Recientes"
36
-        />
37 30
       </IonContent>
38 31
     </IonPage>
39 32
   );

+ 3
- 7
src/pages/LawListPage.tsx View File

@@ -9,7 +9,7 @@ import {
9 9
 } from "@ionic/react";
10 10
 import { alertCircleOutline } from "ionicons/icons";
11 11
 import useSWR from "swr";
12
-import ListComponent from "../components/ListComponent";
12
+import TopicList from "../components/TopicList";
13 13
 import { fetcher } from "../lib/api";
14 14
 import { Topic } from "../types";
15 15
 
@@ -33,15 +33,11 @@ const LawListPage: React.FC = () => {
33 33
             <IonTitle size="large">Leyes</IonTitle>
34 34
           </IonToolbar>
35 35
         </IonHeader>
36
-        <ListComponent
37
-          loading={isLoading}
38
-          laws={data ? data.map((item) => item.name) : []}
39
-        />
40
-
36
+        {data && <TopicList loading={isLoading} topics={data as Topic[]} />}
41 37
         <IonToast
42 38
           icon={alertCircleOutline}
43 39
           color="danger"
44
-          isOpen={error}
40
+          isOpen={error !== undefined}
45 41
           message="Failed to load items."
46 42
           duration={1500}
47 43
         />

+ 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
+*/