From fce11163aa74453bb4d0d9b57d8502a7ceb91edf Mon Sep 17 00:00:00 2001
From: Fedor Katurov <gotham48@gmail.com>
Date: Mon, 4 Apr 2022 15:00:33 +0700
Subject: [PATCH] added notes

---
 .env.local                                    |  8 +--
 src/api/notes/index.ts                        |  8 +++
 src/api/notes/types.ts                        | 12 ++++
 src/components/notes/NoteCard/index.tsx       | 24 ++++++++
 .../notes/NoteCard/styles.module.scss         | 18 ++++++
 src/constants/urls.ts                         |  1 +
 .../settings/SettingsNotes/index.tsx          | 15 ++---
 src/hooks/notes/useGetNotes.ts                | 58 +++++++++++++++++++
 src/types/notes/index.ts                      |  5 ++
 9 files changed, 138 insertions(+), 11 deletions(-)
 create mode 100644 src/api/notes/index.ts
 create mode 100644 src/api/notes/types.ts
 create mode 100644 src/components/notes/NoteCard/index.tsx
 create mode 100644 src/components/notes/NoteCard/styles.module.scss
 create mode 100644 src/hooks/notes/useGetNotes.ts
 create mode 100644 src/types/notes/index.ts

diff --git a/.env.local b/.env.local
index 9c946691..5046a0a7 100644
--- a/.env.local
+++ b/.env.local
@@ -1,6 +1,6 @@
 #NEXT_PUBLIC_API_HOST=https://pig.staging.vault48.org/
 #NEXT_PUBLIC_REMOTE_CURRENT=https://pig.staging.vault48.org/static/
-#NEXT_PUBLIC_API_HOST=http://localhost:8888/
-#NEXT_PUBLIC_REMOTE_CURRENT=http://localhost:8888/static/
-NEXT_PUBLIC_API_HOST=https://pig.vault48.org/
-NEXT_PUBLIC_REMOTE_CURRENT=https://pig.vault48.org/static/
+NEXT_PUBLIC_API_HOST=http://localhost:8888/
+NEXT_PUBLIC_REMOTE_CURRENT=http://localhost:8888/static/
+#NEXT_PUBLIC_API_HOST=https://pig.vault48.org/
+#NEXT_PUBLIC_REMOTE_CURRENT=https://pig.vault48.org/static/
diff --git a/src/api/notes/index.ts b/src/api/notes/index.ts
new file mode 100644
index 00000000..21206e45
--- /dev/null
+++ b/src/api/notes/index.ts
@@ -0,0 +1,8 @@
+import { ApiGetNotesRequest, ApiGetNotesResponse } from '~/api/notes/types';
+import { URLS } from '~/constants/urls';
+import { api, cleanResult } from '~/utils/api';
+
+export const apiGetNotes = ({ limit, offset, search }: ApiGetNotesRequest) =>
+  api
+    .get<ApiGetNotesResponse>(URLS.NOTES, { params: { limit, offset, search } })
+    .then(cleanResult);
diff --git a/src/api/notes/types.ts b/src/api/notes/types.ts
new file mode 100644
index 00000000..921b686e
--- /dev/null
+++ b/src/api/notes/types.ts
@@ -0,0 +1,12 @@
+import { Note } from '~/types/notes';
+
+export interface ApiGetNotesRequest {
+  limit: number;
+  offset: number;
+  search: string;
+}
+
+export interface ApiGetNotesResponse {
+  list: Note[];
+  totalCount: number;
+}
diff --git a/src/components/notes/NoteCard/index.tsx b/src/components/notes/NoteCard/index.tsx
new file mode 100644
index 00000000..41392263
--- /dev/null
+++ b/src/components/notes/NoteCard/index.tsx
@@ -0,0 +1,24 @@
+import React, { VFC } from 'react';
+
+import { Card } from '~/components/containers/Card';
+import { Markdown } from '~/components/containers/Markdown';
+import { Padder } from '~/components/containers/Padder';
+import { formatText, getPrettyDate } from '~/utils/dom';
+
+import styles from './styles.module.scss';
+
+interface NoteCardProps {
+  content: string;
+  createdAt: string;
+}
+
+const NoteCard: VFC<NoteCardProps> = ({ content, createdAt }) => (
+  <Card className={styles.note}>
+    <Padder>
+      <Markdown className={styles.wrap} dangerouslySetInnerHTML={{ __html: formatText(content) }} />
+    </Padder>
+    <Padder className={styles.footer}>{getPrettyDate(createdAt)}</Padder>
+  </Card>
+);
+
+export { NoteCard };
diff --git a/src/components/notes/NoteCard/styles.module.scss b/src/components/notes/NoteCard/styles.module.scss
new file mode 100644
index 00000000..4a378faa
--- /dev/null
+++ b/src/components/notes/NoteCard/styles.module.scss
@@ -0,0 +1,18 @@
+@import "src/styles/variables";
+@import "src/styles/mixins";
+
+.note {
+  min-width: 0;
+  word-break: break-word;
+  padding: 0;
+
+  & > * {
+    @include row_shadow;
+  }
+}
+
+.footer {
+  font: $font_12_regular;
+  text-align: right;
+  opacity: 0.5;
+}
diff --git a/src/constants/urls.ts b/src/constants/urls.ts
index 5b041551..d9214301 100644
--- a/src/constants/urls.ts
+++ b/src/constants/urls.ts
@@ -22,6 +22,7 @@ export const URLS = {
     NOTES: '/settings/notes',
     TRASH: '/settings/trash',
   },
+  NOTES: '/notes/',
 };
 
 export const ImagePresets = {
diff --git a/src/containers/settings/SettingsNotes/index.tsx b/src/containers/settings/SettingsNotes/index.tsx
index 6420bf08..54b31127 100644
--- a/src/containers/settings/SettingsNotes/index.tsx
+++ b/src/containers/settings/SettingsNotes/index.tsx
@@ -5,12 +5,16 @@ import Masonry from 'react-masonry-css';
 import { Card } from '~/components/containers/Card';
 import { Filler } from '~/components/containers/Filler';
 import { Group } from '~/components/containers/Group';
+import { Markdown } from '~/components/containers/Markdown';
 import { Padder } from '~/components/containers/Padder';
 import { Button } from '~/components/input/Button';
 import { Icon } from '~/components/input/Icon';
 import { InputText } from '~/components/input/InputText';
 import { Textarea } from '~/components/input/Textarea';
 import { HorizontalMenu } from '~/components/menu/HorizontalMenu';
+import { NoteCard } from '~/components/notes/NoteCard';
+import { useGetNotes } from '~/hooks/notes/useGetNotes';
+import { formatText } from '~/utils/dom';
 
 import styles from './styles.module.scss';
 
@@ -21,14 +25,9 @@ const breakpointCols = {
   1280: 1,
 };
 
-const sampleNotes = [...new Array(40)].map((_, i) => (
-  <Card key={i} style={{ height: Math.random() * 400 + 50 }}>
-    {i}
-  </Card>
-));
-
 const SettingsNotes: VFC<SettingsNotesProps> = () => {
   const [text, setText] = useState('');
+  const { notes } = useGetNotes('');
 
   return (
     <div>
@@ -61,7 +60,9 @@ const SettingsNotes: VFC<SettingsNotesProps> = () => {
           </Group>
         </Card>
 
-        {sampleNotes}
+        {notes.map(note => (
+          <NoteCard key={note.id} content={note.content} createdAt={note.created_at} />
+        ))}
       </Masonry>
     </div>
   );
diff --git a/src/hooks/notes/useGetNotes.ts b/src/hooks/notes/useGetNotes.ts
new file mode 100644
index 00000000..2ff6cf73
--- /dev/null
+++ b/src/hooks/notes/useGetNotes.ts
@@ -0,0 +1,58 @@
+import { useCallback, useMemo } from 'react';
+
+import useSWRInfinite, { SWRInfiniteKeyLoader } from 'swr/infinite';
+
+import { getLabNodes } from '~/api/lab';
+import { apiGetNotes } from '~/api/notes';
+import { ApiGetNotesRequest } from '~/api/notes/types';
+import { useAuth } from '~/hooks/auth/useAuth';
+import { useUser } from '~/hooks/auth/useUser';
+import { GetLabNodesRequest, ILabNode, LabNodesSort } from '~/types/lab';
+import { flatten, uniqBy } from '~/utils/ramda';
+
+const DEFAULT_COUNT = 20;
+
+const getKey: (isUser: boolean, search: string) => SWRInfiniteKeyLoader = (isUser, search) => (
+  index,
+  prev: ILabNode[]
+) => {
+  if (!isUser) return null;
+  if (index > 0 && (!prev?.length || prev.length < 20)) return null;
+
+  const props: GetLabNodesRequest = {
+    limit: DEFAULT_COUNT,
+    offset: index * DEFAULT_COUNT,
+    search: search || '',
+  };
+
+  return JSON.stringify(props);
+};
+
+const parseKey = (key: string): ApiGetNotesRequest => {
+  try {
+    return JSON.parse(key);
+  } catch (error) {
+    return { limit: DEFAULT_COUNT, offset: 0, search: '' };
+  }
+};
+
+export const useGetNotes = (search: string) => {
+  const { isUser } = useAuth();
+
+  const { data, isValidating, size, setSize, mutate } = useSWRInfinite(
+    getKey(isUser, search),
+    async (key: string) => {
+      const result = await apiGetNotes(parseKey(key));
+      return result.list;
+    },
+    {
+      dedupingInterval: 300,
+    }
+  );
+
+  const notes = useMemo(() => uniqBy(n => n.id, flatten(data || [])), [data]);
+  const hasMore = (data?.[size - 1]?.length || 0) >= 1;
+  const loadMore = useCallback(() => setSize(size + 1), [setSize, size]);
+
+  return { notes, hasMore, loadMore, isLoading: !data && isValidating };
+};
diff --git a/src/types/notes/index.ts b/src/types/notes/index.ts
new file mode 100644
index 00000000..0ae0e730
--- /dev/null
+++ b/src/types/notes/index.ts
@@ -0,0 +1,5 @@
+export interface Note {
+  id: number;
+  content: string;
+  created_at: string;
+}