From 8aa4956d30947aa25ddc3c1e99d219f2324feab4 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Fri, 6 Sep 2024 12:10:13 +0700 Subject: [PATCH 01/18] check root cause error when page changing from article detail to article list --- .../content/article/[documentId]/page.tsx | 16 +++++++++------- hosting/src/components/Tiptap/TiptapEditor.tsx | 10 +++++----- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/hosting/src/app/(protected)/content/article/[documentId]/page.tsx b/hosting/src/app/(protected)/content/article/[documentId]/page.tsx index 4b580e00..e5ef35ce 100644 --- a/hosting/src/app/(protected)/content/article/[documentId]/page.tsx +++ b/hosting/src/app/(protected)/content/article/[documentId]/page.tsx @@ -100,18 +100,20 @@ export default function DocumentDetailsPage() { + + {document?.data.content && ( + + )} ) : ( )} - - ); } diff --git a/hosting/src/components/Tiptap/TiptapEditor.tsx b/hosting/src/components/Tiptap/TiptapEditor.tsx index fe296386..b2173181 100644 --- a/hosting/src/components/Tiptap/TiptapEditor.tsx +++ b/hosting/src/components/Tiptap/TiptapEditor.tsx @@ -1,7 +1,7 @@ "use client"; -import BubbleMenu from "@/components/Tiptap/BubbleMenu"; +// import BubbleMenu from "@/components/Tiptap/BubbleMenu"; import CodeBlock from "@/components/Tiptap/CodeBlock"; -import FloatingMenu from "@/components/Tiptap/FloatingMenu"; +// import FloatingMenu from "@/components/Tiptap/FloatingMenu"; import Loader from "@/components/common/Loader"; import BulletList from "@tiptap/extension-bullet-list"; import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; @@ -121,11 +121,11 @@ export default function TiptapEditor(props: TiptapEditorProps) { }> {editor && ( <> - - + {/* + */} + )} - ); } From 420519de9b0daa5ece0dfa4d45cf62f2f6360cf2 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Fri, 6 Sep 2024 13:21:53 +0700 Subject: [PATCH 02/18] fix ssr error in tiptap editor component --- .../content/article/[documentId]/page.tsx | 9 +++++---- hosting/src/components/Tiptap/BubbleMenu.tsx | 1 + hosting/src/components/Tiptap/CodeBlock.tsx | 1 + hosting/src/components/Tiptap/FloatingMenu.tsx | 1 + hosting/src/components/Tiptap/TiptapEditor.tsx | 13 ++++++------- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/hosting/src/app/(protected)/content/article/[documentId]/page.tsx b/hosting/src/app/(protected)/content/article/[documentId]/page.tsx index e5ef35ce..c2df410e 100644 --- a/hosting/src/app/(protected)/content/article/[documentId]/page.tsx +++ b/hosting/src/app/(protected)/content/article/[documentId]/page.tsx @@ -6,14 +6,15 @@ import Notification from "@/components/common/Notification"; import PageHeader from "@/components/common/PageHeader"; import {useCrudTanamDocument, useTanamDocument} from "@/hooks/useTanamDocuments"; import {UserNotification} from "@/models/UserNotification"; -import dynamic from "next/dynamic"; +// import dynamic from "next/dynamic"; +import TiptapEditor from "@/components/Tiptap/TiptapEditor"; import {useParams, useRouter} from "next/navigation"; import {Suspense, useEffect, useState} from "react"; // TiptapEditor is also detected as ssr, even though it uses "use client" :( -const TiptapEditor = dynamic(() => import("@/components/Tiptap/TiptapEditor"), { - ssr: false, -}); +// const TiptapEditor = dynamic(() => import("@/components/Tiptap/TiptapEditor"), { +// ssr: false, +// }); export default function DocumentDetailsPage() { const router = useRouter(); diff --git a/hosting/src/components/Tiptap/BubbleMenu.tsx b/hosting/src/components/Tiptap/BubbleMenu.tsx index 04ecdaa1..ead373d7 100644 --- a/hosting/src/components/Tiptap/BubbleMenu.tsx +++ b/hosting/src/components/Tiptap/BubbleMenu.tsx @@ -1,3 +1,4 @@ +"use client"; import {Editor, BubbleMenu as TiptapBubbleMenu} from "@tiptap/react"; import {useCallback, useState} from "react"; import "./styles/bubble-menu.scss"; diff --git a/hosting/src/components/Tiptap/CodeBlock.tsx b/hosting/src/components/Tiptap/CodeBlock.tsx index 19217c82..2e889e0e 100644 --- a/hosting/src/components/Tiptap/CodeBlock.tsx +++ b/hosting/src/components/Tiptap/CodeBlock.tsx @@ -1,3 +1,4 @@ +"use client"; import {NodeViewContent, NodeViewWrapper} from "@tiptap/react"; import "./styles/code-block.scss"; diff --git a/hosting/src/components/Tiptap/FloatingMenu.tsx b/hosting/src/components/Tiptap/FloatingMenu.tsx index d0baefd8..9925d090 100644 --- a/hosting/src/components/Tiptap/FloatingMenu.tsx +++ b/hosting/src/components/Tiptap/FloatingMenu.tsx @@ -1,3 +1,4 @@ +"use client"; import {Editor} from "@tiptap/react"; import "./styles/floating-menu.scss"; diff --git a/hosting/src/components/Tiptap/TiptapEditor.tsx b/hosting/src/components/Tiptap/TiptapEditor.tsx index b2173181..5efa8fd2 100644 --- a/hosting/src/components/Tiptap/TiptapEditor.tsx +++ b/hosting/src/components/Tiptap/TiptapEditor.tsx @@ -1,7 +1,7 @@ "use client"; -// import BubbleMenu from "@/components/Tiptap/BubbleMenu"; +import BubbleMenu from "@/components/Tiptap/BubbleMenu"; import CodeBlock from "@/components/Tiptap/CodeBlock"; -// import FloatingMenu from "@/components/Tiptap/FloatingMenu"; +import FloatingMenu from "@/components/Tiptap/FloatingMenu"; import Loader from "@/components/common/Loader"; import BulletList from "@tiptap/extension-bullet-list"; import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; @@ -120,11 +120,10 @@ export default function TiptapEditor(props: TiptapEditorProps) { return ( }> {editor && ( - <> - {/* - */} - - + + + + )} ); From aa0cc47948b3c9cf076d5934aa53fc8af0d545f5 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Fri, 6 Sep 2024 13:58:50 +0700 Subject: [PATCH 03/18] fix acceptfiletype enum --- shared/src/definitions/AcceptFileType.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/shared/src/definitions/AcceptFileType.ts b/shared/src/definitions/AcceptFileType.ts index 7c1e2b9c..3195f04b 100644 --- a/shared/src/definitions/AcceptFileType.ts +++ b/shared/src/definitions/AcceptFileType.ts @@ -1,7 +1,13 @@ // Enum to define acceptable file types for the Dropzone component export enum AcceptFileType { AllImages = "image/*", - Images = "image/jpg, image/jpeg, image/png, image/svg", + Jpg = "image/jpg", + Jpeg = "image/jpeg", + Png = "image/png", + Svg = "image/svg+xml", + Gif = "image/gif", + Webp = "image/webp", + Images = `${AcceptFileType.Jpg}, ${AcceptFileType.Jpeg}, ${AcceptFileType.Png}, ${AcceptFileType.Svg}`, Pdf = "application/pdf", Word = "application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document", Excel = "application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", @@ -11,5 +17,14 @@ export enum AcceptFileType { Video = "video/*", Zip = "application/zip, application/x-rar-compressed, application/x-7z-compressed", Csv = "text/csv", + AllAudios = "audio/*", + Mp3 = "audio/mpeg", + Wav = "audio/wav", + Ogg = "audio/ogg", + M4a = "audio/x-m4a", + Flac = "audio/flac", + Aac = "audio/aac", + Wma = "audio/x-ms-wma", + Audios = `${AcceptFileType.Mp3}, ${AcceptFileType.Wav}, ${AcceptFileType.Ogg}`, AllFiles = "*/*", } From 4b972bbf5fa66cdc2f7d970e73332ea228ba2031 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Fri, 6 Sep 2024 14:13:17 +0700 Subject: [PATCH 04/18] missing genai apikey --- hosting/.env.example | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 hosting/.env.example diff --git a/hosting/.env.example b/hosting/.env.example new file mode 100644 index 00000000..35235649 --- /dev/null +++ b/hosting/.env.example @@ -0,0 +1,9 @@ +GOOGLE_GENAI_API_KEY= + +NEXT_PUBLIC_FIREBASE_API_KEY= +NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN= +NEXT_PUBLIC_FIREBASE_DATABASE_URL= +NEXT_PUBLIC_FIREBASE_PROJECT_ID= +NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET= +NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID= +NEXT_PUBLIC_FIREBASE_APP_ID= From e29d8604da8e3e27ca4281a0a82fb8e61093f157 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Fri, 6 Sep 2024 14:19:23 +0700 Subject: [PATCH 05/18] still get error navigator is not defined when build --- .../app/(protected)/content/article/page.tsx | 21 +++++++++++++------ hosting/src/utils/fileUpload.ts | 4 ++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/hosting/src/app/(protected)/content/article/page.tsx b/hosting/src/app/(protected)/content/article/page.tsx index 7ed3ca61..824e8462 100644 --- a/hosting/src/app/(protected)/content/article/page.tsx +++ b/hosting/src/app/(protected)/content/article/page.tsx @@ -4,24 +4,26 @@ import Loader from "@/components/common/Loader"; import Notification from "@/components/common/Notification"; import PageHeader from "@/components/common/PageHeader"; import {DocumentTypeGenericList} from "@/components/DocumentType/DocumentTypeGenericList"; -import FilePicker from "@/components/FilePicker"; +import {Dropzone} from "@/components/Form"; import {Modal} from "@/components/Modal"; +import VoiceRecorder from "@/components/VoiceRecorder"; import {useAuthentication} from "@/hooks/useAuthentication"; import {ProcessingState, useGenkitArticle} from "@/hooks/useGenkitArticle"; import {useCrudTanamDocument, useTanamDocuments} from "@/hooks/useTanamDocuments"; import {useTanamDocumentType} from "@/hooks/useTanamDocumentTypes"; import {UserNotification} from "@/models/UserNotification"; import {base64ToFile} from "@/plugins/fileUpload"; -import dynamic from "next/dynamic"; +// import dynamic from "next/dynamic"; import {useRouter} from "next/navigation"; import {Suspense, useEffect, useState} from "react"; +import {AcceptFileType} from "tanam-shared/definitions/AcceptFileType"; // I don't know why this component always errors // when built because this component is still detected as a component rendered on the server. // Even though I've used "use client" inside the component :( -const VoiceRecorder = dynamic(() => import("@/components/VoiceRecorder"), { - ssr: false, -}); +// const VoiceRecorder = dynamic(() => import("@/components/VoiceRecorder"), { +// ssr: false, +// }); export default function DocumentTypeDocumentsPage() { const router = useRouter(); @@ -168,7 +170,14 @@ export default function DocumentTypeDocumentsPage() { <>
Or
- + { + if (!fileBlob) return; + + handleFileSelect(fileBlob); + }} + /> )} diff --git a/hosting/src/utils/fileUpload.ts b/hosting/src/utils/fileUpload.ts index 13d3e90d..c2323bc0 100644 --- a/hosting/src/utils/fileUpload.ts +++ b/hosting/src/utils/fileUpload.ts @@ -59,6 +59,10 @@ export function getAcceptDescription(accept: AcceptFileType): string { return "Any image type"; case AcceptFileType.Images: return "Images (JPG, PNG, SVG)"; + case AcceptFileType.AllAudios: + return "Any audio type"; + case AcceptFileType.Audios: + return "Audios (MP3, WAV, OGG)"; case AcceptFileType.Pdf: return "PDF files"; case AcceptFileType.Word: From c79059d887e3f454fa11e4648ad9ae2f42672b18 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Fri, 6 Sep 2024 14:58:06 +0700 Subject: [PATCH 06/18] fix peaks.js ssr compatability --- hosting/src/components/VoiceRecorder.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/hosting/src/components/VoiceRecorder.tsx b/hosting/src/components/VoiceRecorder.tsx index 8bf5e967..d197cb41 100644 --- a/hosting/src/components/VoiceRecorder.tsx +++ b/hosting/src/components/VoiceRecorder.tsx @@ -1,6 +1,5 @@ "use client"; import {TanamSpeechRecognition} from "@/models/TanamSpeechRecognition"; -import Peaks from "peaks.js"; import {useEffect, useRef, useState} from "react"; interface VoiceRecorderProps { @@ -170,9 +169,15 @@ export default function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { /** * Loads the audio buffer and initializes Peak.js with the given options. */ - function initSoundWave() { + async function initSoundWave() { resetSoundWave(); + // SSR Compatability (https://github.com/bbc/peaks.js/issues/335#issuecomment-682223058) + /* eslint-disable */ + const module = await import("peaks.js"); + const Peaks = module.default; + /* eslint-enable */ + // Peak.js options configuration (https://www.npmjs.com/package/peaks.js/v/0.18.1#configuration) const options = { overview: { From 28658fc2ae912a7d17a43f6b4de302a70640ce58 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Mon, 23 Sep 2024 15:31:15 +0700 Subject: [PATCH 07/18] still getting error circullar dependency between domain-shared and domain-frontend --- README.md | 2 +- apps/cms/.env.local.example | 2 + apps/cms/src/components/Form/Dropzone.tsx | 2 +- apps/cms/src/hooks/useFirebaseStorage.tsx | 2 +- libs/domain-frontend/src/models/TanamUser.ts | 2 +- .../src/definitions/AcceptFileType.ts | 2 +- .../src/utils/documentTypeGenerator.ts | 106 ------------------ libs/domain-shared/src/utils/index.ts | 1 - 8 files changed, 7 insertions(+), 112 deletions(-) delete mode 100644 libs/domain-shared/src/utils/documentTypeGenerator.ts diff --git a/README.md b/README.md index 79cc068e..3bf3abbd 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Install all dependencies and serve locally. ```sh npm install -npm serve +npm run serve ``` ## Deploy to Firebase diff --git a/apps/cms/.env.local.example b/apps/cms/.env.local.example index 14e24ee0..35235649 100644 --- a/apps/cms/.env.local.example +++ b/apps/cms/.env.local.example @@ -1,3 +1,5 @@ +GOOGLE_GENAI_API_KEY= + NEXT_PUBLIC_FIREBASE_API_KEY= NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN= NEXT_PUBLIC_FIREBASE_DATABASE_URL= diff --git a/apps/cms/src/components/Form/Dropzone.tsx b/apps/cms/src/components/Form/Dropzone.tsx index b3efd71c..35bc9884 100644 --- a/apps/cms/src/components/Form/Dropzone.tsx +++ b/apps/cms/src/components/Form/Dropzone.tsx @@ -1,7 +1,7 @@ "use client"; import {AcceptFileType} from "@tanam/domain-frontend"; +import {getAcceptDescription, isFileAccepted} from "@tanam/domain-shared"; import React, {useEffect, useRef, useState} from "react"; -import {getAcceptDescription, isFileAccepted} from "../../utils/fileUpload"; import "./styles/dropzone.scss"; export interface DropzoneProps { diff --git a/apps/cms/src/hooks/useFirebaseStorage.tsx b/apps/cms/src/hooks/useFirebaseStorage.tsx index da1c9dfe..9d04c800 100644 --- a/apps/cms/src/hooks/useFirebaseStorage.tsx +++ b/apps/cms/src/hooks/useFirebaseStorage.tsx @@ -1,8 +1,8 @@ import {UserNotification} from "@tanam/domain-frontend"; +import {base64ToBlob} from "@tanam/domain-shared"; import {getDownloadURL, ref, uploadBytes} from "firebase/storage"; import {useState} from "react"; import {storage} from "../plugins/firebase"; -import {base64ToBlob} from "../utils/fileUpload"; interface FirebaseStorageHook { isLoading: boolean; diff --git a/libs/domain-frontend/src/models/TanamUser.ts b/libs/domain-frontend/src/models/TanamUser.ts index c94620d8..f5982986 100644 --- a/libs/domain-frontend/src/models/TanamUser.ts +++ b/libs/domain-frontend/src/models/TanamUser.ts @@ -1,4 +1,4 @@ -import {TanamRole, TanamUserBase} from "@tanam/domain-shared"; +import {TanamUserBase} from "@tanam/domain-shared"; import {DocumentSnapshot, FieldValue, serverTimestamp, Timestamp} from "firebase/firestore"; export class TanamUser extends TanamUserBase { diff --git a/libs/domain-shared/src/definitions/AcceptFileType.ts b/libs/domain-shared/src/definitions/AcceptFileType.ts index 3195f04b..252db1d5 100644 --- a/libs/domain-shared/src/definitions/AcceptFileType.ts +++ b/libs/domain-shared/src/definitions/AcceptFileType.ts @@ -13,7 +13,6 @@ export enum AcceptFileType { Excel = "application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", PowerPoint = "application/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.presentationml.presentation", Text = "text/plain", - Audio = "audio/*", Video = "video/*", Zip = "application/zip, application/x-rar-compressed, application/x-7z-compressed", Csv = "text/csv", @@ -25,6 +24,7 @@ export enum AcceptFileType { Flac = "audio/flac", Aac = "audio/aac", Wma = "audio/x-ms-wma", + Audio = `${AcceptFileType.AllAudios}`, Audios = `${AcceptFileType.Mp3}, ${AcceptFileType.Wav}, ${AcceptFileType.Ogg}`, AllFiles = "*/*", } diff --git a/libs/domain-shared/src/utils/documentTypeGenerator.ts b/libs/domain-shared/src/utils/documentTypeGenerator.ts deleted file mode 100644 index e1b68724..00000000 --- a/libs/domain-shared/src/utils/documentTypeGenerator.ts +++ /dev/null @@ -1,106 +0,0 @@ -import {LocalizedString, TanamDocumentField, TanamDocumentType} from "@tanam/domain-frontend"; - -export interface IDocumentTypeDataResult { - data: TanamDocumentType; - fields: TanamDocumentField[]; -} - -export function getDocumentTypeArticle(): IDocumentTypeDataResult { - const data = new TanamDocumentType( - "article", - new LocalizedString({en: "Article"}), - new LocalizedString({en: "Articles"}), - new LocalizedString({en: "Article such as a blog post or a published article."}), - "title", - true, - ); - - const fields: TanamDocumentField[] = [ - new TanamDocumentField("title", { - weight: 1000, - title: new LocalizedString({en: "Title"}), - description: new LocalizedString({en: "Article title"}), - type: "string", - validators: ["required"], - }), - new TanamDocumentField("featuredImage", { - weight: 2000, - title: new LocalizedString({en: "Featured image"}), - description: new LocalizedString({ - en: "Featured image to display on the article page and to use for social media sharing.", - }), - type: "image", - validators: ["required"], - }), - new TanamDocumentField("content", { - weight: 3000, - title: new LocalizedString({en: "Content"}), - description: new LocalizedString({ - en: "Content body of the article.", - }), - type: "html", - validators: ["required"], - }), - new TanamDocumentField("canonicalUrl", { - weight: 4000, - title: new LocalizedString({en: "Canonical URL"}), - description: new LocalizedString({ - en: "Content body of the article.", - }), - type: "string", - validators: ["url"], - }), - new TanamDocumentField("tags", { - weight: 5000, - title: new LocalizedString({en: "Tags"}), - description: new LocalizedString({ - en: "Tags to categorize the article.", - }), - type: "array:string", - validators: ["url"], - }), - ]; - - return {data, fields}; -} - -export function getDocumentTypePerson(): IDocumentTypeDataResult { - const data = new TanamDocumentType( - "person", - new LocalizedString({en: "Person"}), - new LocalizedString({en: "People"}), - new LocalizedString({en: "People such as authors, contributors, and staff members."}), - "name", - true, - ); - - const fields: TanamDocumentField[] = [ - new TanamDocumentField("name", { - weight: 1000, - title: new LocalizedString({en: "Name"}), - description: new LocalizedString({en: "Full name"}), - type: "string", - validators: ["required"], - }), - new TanamDocumentField("image", { - weight: 2000, - title: new LocalizedString({en: "Image"}), - description: new LocalizedString({ - en: "Photo of the person.", - }), - type: "image", - validators: [], - }), - new TanamDocumentField("bio", { - weight: 3000, - title: new LocalizedString({en: "Bio"}), - description: new LocalizedString({ - en: "Bio or description of the person.", - }), - type: "text-paragraph", - validators: [], - }), - ]; - - return {data, fields}; -} diff --git a/libs/domain-shared/src/utils/index.ts b/libs/domain-shared/src/utils/index.ts index be92329d..3d08236c 100644 --- a/libs/domain-shared/src/utils/index.ts +++ b/libs/domain-shared/src/utils/index.ts @@ -1,3 +1,2 @@ export * from "./date"; -export * from "./documentTypeGenerator"; export * from "./fileUpload"; From 37a3bf011d57b3a54d68a0b82942c5c39599d008 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Mon, 23 Sep 2024 16:25:19 +0700 Subject: [PATCH 08/18] fix circular dependency error --- .../app/(protected)/content/article/page.tsx | 13 +- apps/cms/src/components/Form/Dropzone.tsx | 2 +- apps/cms/src/hooks/useFirebaseStorage.tsx | 4 +- libs/domain-shared/src/index.ts | 1 - libs/domain-shared/src/utils/date.ts | 57 ---- libs/domain-shared/src/utils/fileUpload.ts | 102 ------ libs/domain-shared/src/utils/index.ts | 2 - libs/ui-components/src/Form/Dropzone.tsx | 128 -------- libs/ui-components/src/Form/index.tsx | 1 - libs/ui-components/src/VoiceRecorder.tsx | 290 ------------------ libs/ui-components/src/index.ts | 1 - 11 files changed, 6 insertions(+), 595 deletions(-) delete mode 100644 libs/domain-shared/src/utils/date.ts delete mode 100644 libs/domain-shared/src/utils/fileUpload.ts delete mode 100644 libs/domain-shared/src/utils/index.ts delete mode 100644 libs/ui-components/src/Form/Dropzone.tsx delete mode 100644 libs/ui-components/src/VoiceRecorder.tsx diff --git a/apps/cms/src/app/(protected)/content/article/page.tsx b/apps/cms/src/app/(protected)/content/article/page.tsx index be0564fb..4bbaeac1 100644 --- a/apps/cms/src/app/(protected)/content/article/page.tsx +++ b/apps/cms/src/app/(protected)/content/article/page.tsx @@ -2,19 +2,12 @@ import {UserNotification} from "@tanam/domain-frontend"; import {AcceptFileType} from "@tanam/domain-shared"; -import { - Button, - DocumentTypeGenericList, - Dropzone, - Loader, - Modal, - Notification, - PageHeader, - VoiceRecorder, -} from "@tanam/ui-components"; +import {Button, DocumentTypeGenericList, Loader, Modal, Notification, PageHeader} from "@tanam/ui-components"; // import dynamic from "next/dynamic"; import {useRouter} from "next/navigation"; import {Suspense, useEffect, useState} from "react"; +import {Dropzone} from "../../../../components/Form/Dropzone"; +import VoiceRecorder from "../../../../components/VoiceRecorder"; import {useAuthentication} from "../../../../hooks/useAuthentication"; import {ProcessingState, useGenkitArticle} from "../../../../hooks/useGenkitArticle"; import {useCrudTanamDocument, useTanamDocuments} from "../../../../hooks/useTanamDocuments"; diff --git a/apps/cms/src/components/Form/Dropzone.tsx b/apps/cms/src/components/Form/Dropzone.tsx index 35bc9884..a47d8418 100644 --- a/apps/cms/src/components/Form/Dropzone.tsx +++ b/apps/cms/src/components/Form/Dropzone.tsx @@ -1,7 +1,7 @@ "use client"; import {AcceptFileType} from "@tanam/domain-frontend"; -import {getAcceptDescription, isFileAccepted} from "@tanam/domain-shared"; import React, {useEffect, useRef, useState} from "react"; +import {getAcceptDescription, isFileAccepted} from "./../../utils/fileUpload"; import "./styles/dropzone.scss"; export interface DropzoneProps { diff --git a/apps/cms/src/hooks/useFirebaseStorage.tsx b/apps/cms/src/hooks/useFirebaseStorage.tsx index 9d04c800..8e4553a4 100644 --- a/apps/cms/src/hooks/useFirebaseStorage.tsx +++ b/apps/cms/src/hooks/useFirebaseStorage.tsx @@ -1,8 +1,8 @@ import {UserNotification} from "@tanam/domain-frontend"; -import {base64ToBlob} from "@tanam/domain-shared"; import {getDownloadURL, ref, uploadBytes} from "firebase/storage"; import {useState} from "react"; -import {storage} from "../plugins/firebase"; +import {storage} from "./../plugins/firebase"; +import {base64ToBlob} from "./../utils/fileUpload"; interface FirebaseStorageHook { isLoading: boolean; diff --git a/libs/domain-shared/src/index.ts b/libs/domain-shared/src/index.ts index 688d4c85..4d0b15c1 100644 --- a/libs/domain-shared/src/index.ts +++ b/libs/domain-shared/src/index.ts @@ -1,4 +1,3 @@ export * from "./definitions"; export * from "./models"; export * from "./schemas"; -export * from "./utils"; diff --git a/libs/domain-shared/src/utils/date.ts b/libs/domain-shared/src/utils/date.ts deleted file mode 100644 index b1ca2d8e..00000000 --- a/libs/domain-shared/src/utils/date.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Format date based on a given format string - * @param {Date} date - The date object to format - * @param {string} format - The format string specifying the desired date format - * @return {string} The formatted date string based on the provided format - */ -export function formatDate(date: Date, format: string): string { - const map: {[key: string]: string} = { - YYYY: date.getFullYear().toString(), - // Month as two digits - MM: String(date.getMonth() + 1).padStart(2, "0"), - // Day of the month as two digits - DD: String(date.getDate()).padStart(2, "0"), - // Hour (12-hour clock) - hh: String(date.getHours() % 12 || 12).padStart(2, "0"), - // Hour (24-hour clock) - HH: String(date.getHours()).padStart(2, "0"), - // Minutes as two digits - mm: String(date.getMinutes()).padStart(2, "0"), - // Seconds as two digits - ss: String(date.getSeconds()).padStart(2, "0"), - // AM/PM marker - A: date.getHours() >= 12 ? "PM" : "AM", - // am/pm marker - a: date.getHours() >= 12 ? "pm" : "am", - // Full month name - MMMM: date.toLocaleString("default", {month: "long"}), - // Short month name - MMM: date.toLocaleString("default", {month: "short"}), - // Full weekday name - dddd: date.toLocaleString("default", {weekday: "long"}), - // Short weekday name - ddd: date.toLocaleString("default", {weekday: "short"}), - }; - - // Regex for AM/PM markers - const amPmRegex = /A|a/g; - // Regex for other tokens - const otherTokensRegex = /MMMM|MMM|dddd|ddd|YYYY|MM|DD|hh|HH|mm|ss/g; - - // First replace the AM/PM markers - let result = format.replace(amPmRegex, (matched) => map[matched] || matched); - - // Then replace the other tokens - result = result.replace(otherTokensRegex, (matched) => map[matched] || matched); - - return result; -} - -/** - * Get the current date formatted based on a given format string - * @param {string} format - The format string specifying the desired date format - * @return {string} The current date formatted based on the provided format - */ -export function getCurrentDateFormatted(format: string): string { - return formatDate(new Date(), format); -} diff --git a/libs/domain-shared/src/utils/fileUpload.ts b/libs/domain-shared/src/utils/fileUpload.ts deleted file mode 100644 index ee918952..00000000 --- a/libs/domain-shared/src/utils/fileUpload.ts +++ /dev/null @@ -1,102 +0,0 @@ -import {AcceptFileType} from "@tanam/domain-frontend"; - -/** - * Converts a base64 string to a Blob object. - * @param {string} base64 - Base64 string of the file. - * @param {string} contentType - MIME type of the file (e.g., 'image/jpeg', 'application/pdf'). - * @return {Blob} - The Blob object created from the base64 string. - */ -export function base64ToBlob(base64: string, contentType: string): Blob { - const byteCharacters = atob(base64.split(",")[1]); - const byteNumbers = Array.from(byteCharacters, (char) => char.charCodeAt(0)); - const byteArray = new Uint8Array(byteNumbers); - - return new Blob([byteArray], {type: contentType}); -} - -/** - * Gets the file extension based on the MIME type. - * @param {string} contentType - MIME type of the file (e.g., 'image/jpeg', 'application/pdf'). - * @return {string} - File extension based on MIME type. - */ -export function getFileExtension(contentType: string): string { - const mimeTypeToExtension: {[key: string]: string} = { - "image/jpeg": ".jpg", - "image/jpg": ".jpg", - "image/png": ".png", - "image/gif": ".gif", - "image/bmp": ".bmp", - "image/webp": ".webp", - "application/pdf": ".pdf", - "application/msword": ".doc", - "application/vnd.openxmlformats-officedocument.wordprocessingml.document": ".docx", - "application/vnd.ms-excel": ".xls", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx", - "application/vnd.ms-powerpoint": ".ppt", - "application/vnd.openxmlformats-officedocument.presentationml.presentation": ".pptx", - "text/plain": ".txt", - "text/html": ".html", - "text/css": ".css", - "application/javascript": ".js", - "application/json": ".json", - "application/zip": ".zip", - "application/x-rar-compressed": ".rar", - "application/x-7z-compressed": ".7z", - // Add more mappings as needed - }; - - return mimeTypeToExtension[contentType] || ""; -} - -/** - * Utility function to get a user-friendly description of the accepted file types. - * @param {AcceptFileType} accept - The accept enum value. - * @return {string} A descriptive string for the accepted file types. - */ -export function getAcceptDescription(accept: AcceptFileType): string { - switch (accept) { - case AcceptFileType.AllImages: - return "Any image type"; - case AcceptFileType.Images: - return "Images (JPG, PNG, SVG)"; - case AcceptFileType.AllAudios: - return "Any audio type"; - case AcceptFileType.Audios: - return "Audios (MP3, WAV, OGG)"; - case AcceptFileType.Pdf: - return "PDF files"; - case AcceptFileType.Word: - return "Word documents"; - case AcceptFileType.Excel: - return "Excel spreadsheets"; - case AcceptFileType.PowerPoint: - return "PowerPoint presentations"; - case AcceptFileType.Text: - return "Text files"; - case AcceptFileType.Audio: - return "Audio files"; - case AcceptFileType.Video: - return "Video files"; - case AcceptFileType.Zip: - return "Compressed files (ZIP, RAR, 7z)"; - case AcceptFileType.Csv: - return "CSV files"; - case AcceptFileType.AllFiles: - default: - return "Any file type"; - } -} - -/** - * Checks if the file is accepted based on the MIME type. - * @param {File} file - The file object to be checked. - * @param {AcceptFileType} accept - The accepted file type. - * @return {boolean} - True if the file is accepted, otherwise false. - */ -export function isFileAccepted(file: File, accept: AcceptFileType): boolean { - const fileExtension = getFileExtension(file.type); - const acceptedMimeTypes = accept.split(",").map((type) => type.trim()); - - // Check if the file's MIME type matches any of the accepted MIME types - return acceptedMimeTypes.some((type) => type === file.type || fileExtension === getFileExtension(type)); -} diff --git a/libs/domain-shared/src/utils/index.ts b/libs/domain-shared/src/utils/index.ts deleted file mode 100644 index 3d08236c..00000000 --- a/libs/domain-shared/src/utils/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./date"; -export * from "./fileUpload"; diff --git a/libs/ui-components/src/Form/Dropzone.tsx b/libs/ui-components/src/Form/Dropzone.tsx deleted file mode 100644 index d890128e..00000000 --- a/libs/ui-components/src/Form/Dropzone.tsx +++ /dev/null @@ -1,128 +0,0 @@ -"use client"; -import {AcceptFileType} from "@tanam/domain-frontend"; -import {getAcceptDescription, isFileAccepted} from "@tanam/domain-shared"; -import React, {useEffect, useRef, useState} from "react"; -import "./styles/dropzone.scss"; - -export interface DropzoneProps { - value?: string; - disabled?: boolean; - accept?: AcceptFileType; - onChange?: (fileString: string | null, fileBlob: File | null) => void; -} - -/** - * Dropzone component for handling file uploads and sending data to parent without preview. - * @param {DropzoneProps} props - The properties for the dropzone component. - * @return {JSX.Element} The rendered dropzone component. - */ -export function Dropzone({value, disabled, accept = AcceptFileType.AllFiles, onChange}: DropzoneProps): JSX.Element { - const [dragActive, setDragActive] = useState(false); - const inputRef = useRef(null); - - /** - * useEffect hook that resets the file input value when the component unmounts. - */ - useEffect(() => { - return () => { - if (inputRef.current) { - inputRef.current.value = ""; - } - }; - }, [value]); - - /** - * Handles drag events (dragenter, dragover, dragleave) to manage the drag state. - * @param {React.DragEvent} e - The drag event triggered when a file is dragged over the dropzone. - */ - function handleDrag(e: React.DragEvent) { - e.preventDefault(); - e.stopPropagation(); - - if (disabled) return; - - if (e.type === "dragenter" || e.type === "dragover") { - setDragActive(true); - } - - if (e.type === "dragleave") { - setDragActive(false); - } - } - - /** - * Handles the drop event when a file is dropped into the dropzone. - * @param {React.DragEvent} e - The drop event triggered when a file is dropped. - */ - function handleDrop(e: React.DragEvent) { - e.preventDefault(); - e.stopPropagation(); - setDragActive(false); - - if (disabled) return; - - if (e.dataTransfer.files && e.dataTransfer.files[0]) { - const file = e.dataTransfer.files[0]; - - if (!isFileAccepted(file, accept)) { - console.error(`File type not accepted: ${file.name}`); - - return; - } - - handleFile(file, onChange); - } - } - - function handleChange( - e: React.ChangeEvent, - callback?: (fileString: string | null, fileBlob: File | null) => void, - ): void { - if (e.target.files && e.target.files[0]) { - handleFile(e.target.files[0], callback); - } - } - - function handleFile(file: File, callback?: (fileString: string | null, fileBlob: File | null) => void): void { - const reader = new FileReader(); - - reader.onloadend = () => { - if (callback) { - callback(reader.result as string, file); - } - }; - - reader.readAsDataURL(file); - } - - return ( -
-
- handleChange(e, onChange)} - className="absolute inset-0 z-50 m-0 h-full w-full cursor-pointer p-0 opacity-0 outline-none" - /> -
- -

- Click to upload or drag and drop -

-

Allowed types: {getAcceptDescription(accept)}

-
-
-
- ); -} diff --git a/libs/ui-components/src/Form/index.tsx b/libs/ui-components/src/Form/index.tsx index 60d81c9b..897dd50d 100644 --- a/libs/ui-components/src/Form/index.tsx +++ b/libs/ui-components/src/Form/index.tsx @@ -1,7 +1,6 @@ export {Checkbox} from "./Checkbox"; export {DatePicker} from "./DatePicker"; export {Dropdown} from "./Dropdown"; -export {Dropzone} from "./Dropzone"; export {FileUpload} from "./FileUpload"; export {FormGroup} from "./FormGroup"; export {Input} from "./Input"; diff --git a/libs/ui-components/src/VoiceRecorder.tsx b/libs/ui-components/src/VoiceRecorder.tsx deleted file mode 100644 index ba001af5..00000000 --- a/libs/ui-components/src/VoiceRecorder.tsx +++ /dev/null @@ -1,290 +0,0 @@ -"use client"; -import {TanamSpeechRecognition} from "@tanam/domain-frontend"; -import {useEffect, useRef, useState} from "react"; - -interface VoiceRecorderProps { - title?: string; - value: string; - onChange: (value: string) => void; - onLoadingChange?: (isRecording: boolean) => void; - onTranscriptChange?: (transcript: string) => void; -} - -/** - * VoiceRecorder component allows users to record audio, convert it to a base64 string, - * and automatically performs speech recognition to provide a text transcript of the recorded audio. - * - * @param {VoiceRecorderProps} props - The props for the component. - * @return {JSX.Element} The rendered VoiceRecorder component. - */ -export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { - const {title, value, onChange, onTranscriptChange, onLoadingChange} = props; - - const [isRecording, setIsRecording] = useState(false); - const [transcript, setTranscript] = useState(""); - const [audioUrl, setAudioUrl] = useState(""); - - const mediaRecorderRef = useRef(); - const audioChunksRef = useRef([]); - const recognitionRef = useRef(); - const streamRef = useRef(null); - const audioContextRef = useRef(null); - const peaksInstanceRef = useRef(null); - - /** - * Effect to trigger onChange whenever the value prop changes. - */ - useEffect(() => { - if (value) { - onChange(value); - } - }, [value, onChange]); - - /** - * Effect to trigger onLoadingChange whenever the isRecording prop changes. - */ - useEffect(() => { - if (onLoadingChange) { - onLoadingChange(isRecording); - } - }, [isRecording, onLoadingChange]); - - /** - * Effect to trigger initSoundWave whenever the audioUrl changes. - */ - useEffect(() => { - if (audioUrl) { - initSoundWave(); - } - - return () => { - resetSoundWave(); - }; - }, [audioUrl]); - - /** - * Effect hook that sets up the SpeechRecognition instance and its event handlers. - * It handles starting, stopping, and restarting the speech recognition process. - */ - useEffect(() => { - const SpeechRecognition = new TanamSpeechRecognition(); - - if (SpeechRecognition) { - recognitionRef.current = SpeechRecognition; - const recognition = recognitionRef.current; - - if (!recognition) return; - - // Handle the event when speech recognition returns results - recognition.onresult = (event: any) => { - const transcript = Array.from(event.results) - .map((result: any) => result[0]) - .map((result) => result.transcript) - .join(""); - - setTranscript(transcript); // Update local state with the transcript - - if (onTranscriptChange) { - onTranscriptChange(transcript); - } - }; - } - }, [onTranscriptChange, isRecording]); - - /** - * Starts recording audio using the user"s microphone. - * It sets up the MediaRecorder, gathers audio data, and pushes it to the audioChunksRef. - * When recording stops, it converts the audio chunks to a base64 string and passes it to onChange. - */ - async function handleStartRecording() { - if (typeof navigator === "undefined" || typeof window === "undefined") return; - - handleReset(); - setIsRecording(true); - - const stream = await navigator.mediaDevices.getUserMedia({audio: true}); - streamRef.current = stream; - mediaRecorderRef.current = new MediaRecorder(stream); - - const audioContext = new AudioContext(); - audioContextRef.current = audioContext; - - mediaRecorderRef.current.ondataavailable = (event: BlobEvent) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = () => { - const audioBlob = new Blob(audioChunksRef.current, {type: "audio/wav"}); - const reader = new FileReader(); - - reader.onloadend = () => { - if (!reader.result) return; - - const base64String = reader.result?.toString() || ""; - onChange(base64String); - - // Set audio URL for visualization - const url = URL.createObjectURL(audioBlob); - setAudioUrl(url); - }; - - reader.readAsDataURL(audioBlob); - audioChunksRef.current = []; - }; - - mediaRecorderRef.current.start(); - recognitionRef.current?.start(); - } - - /** - * Stops the current audio recording. - */ - function handleStopRecording() { - setIsRecording(false); - - if (!mediaRecorderRef.current || !recognitionRef.current || !streamRef.current) return; - - mediaRecorderRef.current.stop(); - recognitionRef.current.stop(); - // Stop all tracks to turn off the microphone - streamRef.current.getTracks().forEach((track) => track.stop()); - - if (audioContextRef.current && audioContextRef.current.state !== "closed") { - audioContextRef.current.close().then(() => { - audioContextRef.current = null; - }); - } - } - - /** - * Resets the recorded audio by clearing the base64 string. - */ - function handleReset() { - onChange(""); - setTranscript(""); - setAudioUrl(""); - handleStopRecording(); - } - - /** - * Loads the audio buffer and initializes Peak.js with the given options. - */ - async function initSoundWave() { - resetSoundWave(); - - // SSR Compatability (https://github.com/bbc/peaks.js/issues/335#issuecomment-682223058) - /* eslint-disable */ - const module = await import("peaks.js"); - const Peaks = module.default; - /* eslint-enable */ - - // Peak.js options configuration (https://www.npmjs.com/package/peaks.js/v/0.18.1#configuration) - const options = { - overview: { - container: document.getElementById("overviewContainer"), - }, - mediaElement: document.getElementById("audio"), - webAudio: { - audioContext: new AudioContext(), - audioBuffer: null, - multiChannel: false, - }, - height: 200, - waveformBuilderOptions: { - scale: 128, - }, - zoomLevels: [128, 256, 512, 1024, 2048], - logger: console.error.bind(console), - emitCueEvents: false, - segmentStartMarkerColor: "#a0a0a0", - segmentEndMarkerColor: "#a0a0a0", - overviewWaveformColor: "rgba(0,0,0,0.2)", - overviewHighlightColor: "grey", - overviewHighlightOffset: 11, - segmentColor: "rgba(255, 161, 39, 1)", - playheadColor: "rgba(0, 0, 0, 1)", - playheadTextColor: "#aaa", - showPlayheadTime: false, - pointMarkerColor: "#FF0000", - axisGridlineColor: "#ccc", - axisLabelColor: "#aaa", - randomizeSegmentColor: true, - } as any; - - peaksInstanceRef.current = Peaks.init(options, (err) => { - if (err) { - console.error(err.message); - return; - } - }); - } - - function resetSoundWave() { - if (!peaksInstanceRef.current) return; - - peaksInstanceRef.current.destroy(); - } - - return ( -
- {title &&

{title}

} -
- {isRecording ? ( - <> -
- -
- -

Mic's Live!

- - ) : ( - <> -
- -
- -

{!audioUrl ? "Tap & Speak" : "Tap to Try Again"}

- - )} -
- {audioUrl && ( -
-

Your Recording:

-
-
-
- - -
- )} - {transcript && ( -
-

What You Said:

-

{transcript}

-
- )} -
- ); -} diff --git a/libs/ui-components/src/index.ts b/libs/ui-components/src/index.ts index f78afc4d..5ef018ba 100644 --- a/libs/ui-components/src/index.ts +++ b/libs/ui-components/src/index.ts @@ -15,4 +15,3 @@ export * from "./Button"; export * from "./CropImage"; export * from "./FilePicker"; export * from "./Modal"; -export * from "./VoiceRecorder"; From 150600b146b9a02317d5e49efc91544aebd77708 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Mon, 23 Sep 2024 16:51:11 +0700 Subject: [PATCH 09/18] fix error module boundaries --- apps/cms/project.json | 4 +--- .../content/[documentTypeId]/[documentId]/page.tsx | 1 + .../cms/src/app/(protected)/content/article/page.tsx | 12 +----------- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/apps/cms/project.json b/apps/cms/project.json index 44a5fe6b..57ca9a8a 100644 --- a/apps/cms/project.json +++ b/apps/cms/project.json @@ -3,9 +3,7 @@ "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "apps/cms", "projectType": "application", - "tags": [ - "app:frontend" - ], + "tags": ["app:frontend"], "// targets": "to see all targets run: nx show project cms --web", "targets": {} } diff --git a/apps/cms/src/app/(protected)/content/[documentTypeId]/[documentId]/page.tsx b/apps/cms/src/app/(protected)/content/[documentTypeId]/[documentId]/page.tsx index 79f34f97..a884d542 100644 --- a/apps/cms/src/app/(protected)/content/[documentTypeId]/[documentId]/page.tsx +++ b/apps/cms/src/app/(protected)/content/[documentTypeId]/[documentId]/page.tsx @@ -42,6 +42,7 @@ const DocumentDetailsPage = () => { } }, [document, documentTypeId, router]); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const renderFormElement = (field: TanamDocumentField, value: any) => { const formgroupKey = `formgroup-${field.id}`; const inputKey = `input-${field.id}`; diff --git a/apps/cms/src/app/(protected)/content/article/page.tsx b/apps/cms/src/app/(protected)/content/article/page.tsx index 4bbaeac1..181155ab 100644 --- a/apps/cms/src/app/(protected)/content/article/page.tsx +++ b/apps/cms/src/app/(protected)/content/article/page.tsx @@ -1,9 +1,6 @@ "use client"; - -import {UserNotification} from "@tanam/domain-frontend"; -import {AcceptFileType} from "@tanam/domain-shared"; +import {AcceptFileType, UserNotification} from "@tanam/domain-frontend"; import {Button, DocumentTypeGenericList, Loader, Modal, Notification, PageHeader} from "@tanam/ui-components"; -// import dynamic from "next/dynamic"; import {useRouter} from "next/navigation"; import {Suspense, useEffect, useState} from "react"; import {Dropzone} from "../../../../components/Form/Dropzone"; @@ -14,13 +11,6 @@ import {useCrudTanamDocument, useTanamDocuments} from "../../../../hooks/useTana import {useTanamDocumentType} from "../../../../hooks/useTanamDocumentTypes"; import {base64ToFile} from "../../../../plugins/fileUpload"; -// NOTE(Dennis) -// The VoiceRecorder is using `navigator` to access the microphone, which creates issues with server-side rendering. -// The module must be dynamically imported to avoid problems when statically rendered components are generated. -// const VoiceRecorder = dynamic(() => import("../../../../components/VoiceRecorder").then((mod) => mod.default), { -// ssr: false, -// }); - export default function DocumentTypeDocumentsPage() { const router = useRouter(); const {authUser} = useAuthentication(); From ee92a34fd7042f508862fbfd892f587d00a80301 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Tue, 24 Sep 2024 09:44:41 +0700 Subject: [PATCH 10/18] fix wrong position param in TanamDocument --- apps/cms/src/hooks/useTanamDocuments.tsx | 5 ++++- libs/domain-frontend/src/models/TanamDocument.ts | 11 +++++++++-- .../src/DocumentType/DocumentTypeGenericList.tsx | 1 + 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/cms/src/hooks/useTanamDocuments.tsx b/apps/cms/src/hooks/useTanamDocuments.tsx index 5b11a5c3..3d65110a 100644 --- a/apps/cms/src/hooks/useTanamDocuments.tsx +++ b/apps/cms/src/hooks/useTanamDocuments.tsx @@ -44,7 +44,10 @@ export function useTanamDocuments(documentTypeId?: string): UseTanamDocumentsRes const unsubscribe = onSnapshot( q, (snapshot) => { - const documents = snapshot.docs.map((doc) => TanamDocument.fromFirestore(doc)); + const documents = snapshot.docs.map((doc) => { + console.info("doc :: ", doc); + return TanamDocument.fromFirestore(doc); + }); setData(documents); setIsLoading(false); }, diff --git a/libs/domain-frontend/src/models/TanamDocument.ts b/libs/domain-frontend/src/models/TanamDocument.ts index 9d77a810..95841a08 100644 --- a/libs/domain-frontend/src/models/TanamDocument.ts +++ b/libs/domain-frontend/src/models/TanamDocument.ts @@ -12,18 +12,25 @@ export class TanamDocument extends TanamDocumentBase { static fromFirestore(snap: DocumentSnapshot): TanamDocument { const data = snap.data(); + + console.info("data :: ", data); + if (!data) { throw new Error("Document data is undefined"); } - return new TanamDocument( + const result = new TanamDocument( snap.id, data.createdAt, data.updatedAt, - data.data, data.documentType, + data.data, data.revision, data.publishedAt, ); + + console.info("result :: ", result); + + return result; } } diff --git a/libs/ui-components/src/DocumentType/DocumentTypeGenericList.tsx b/libs/ui-components/src/DocumentType/DocumentTypeGenericList.tsx index ccfb1469..475ee3e5 100644 --- a/libs/ui-components/src/DocumentType/DocumentTypeGenericList.tsx +++ b/libs/ui-components/src/DocumentType/DocumentTypeGenericList.tsx @@ -9,6 +9,7 @@ interface TableOverviewGenericProps { } export function DocumentTypeGenericList({documents, documentType, isLoading}: TableOverviewGenericProps) { + console.info("documents :: ", documents); return ( Date: Tue, 24 Sep 2024 11:27:32 +0700 Subject: [PATCH 11/18] remove console --- apps/cms/src/hooks/useTanamDocuments.tsx | 5 +---- libs/domain-frontend/src/models/TanamDocument.ts | 8 +------- .../src/DocumentType/DocumentTypeGenericList.tsx | 1 - 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/apps/cms/src/hooks/useTanamDocuments.tsx b/apps/cms/src/hooks/useTanamDocuments.tsx index 3d65110a..5b11a5c3 100644 --- a/apps/cms/src/hooks/useTanamDocuments.tsx +++ b/apps/cms/src/hooks/useTanamDocuments.tsx @@ -44,10 +44,7 @@ export function useTanamDocuments(documentTypeId?: string): UseTanamDocumentsRes const unsubscribe = onSnapshot( q, (snapshot) => { - const documents = snapshot.docs.map((doc) => { - console.info("doc :: ", doc); - return TanamDocument.fromFirestore(doc); - }); + const documents = snapshot.docs.map((doc) => TanamDocument.fromFirestore(doc)); setData(documents); setIsLoading(false); }, diff --git a/libs/domain-frontend/src/models/TanamDocument.ts b/libs/domain-frontend/src/models/TanamDocument.ts index 95841a08..fc2c907d 100644 --- a/libs/domain-frontend/src/models/TanamDocument.ts +++ b/libs/domain-frontend/src/models/TanamDocument.ts @@ -13,13 +13,11 @@ export class TanamDocument extends TanamDocumentBase { static fromFirestore(snap: DocumentSnapshot): TanamDocument { const data = snap.data(); - console.info("data :: ", data); - if (!data) { throw new Error("Document data is undefined"); } - const result = new TanamDocument( + return new TanamDocument( snap.id, data.createdAt, data.updatedAt, @@ -28,9 +26,5 @@ export class TanamDocument extends TanamDocumentBase { data.revision, data.publishedAt, ); - - console.info("result :: ", result); - - return result; } } diff --git a/libs/ui-components/src/DocumentType/DocumentTypeGenericList.tsx b/libs/ui-components/src/DocumentType/DocumentTypeGenericList.tsx index 475ee3e5..ccfb1469 100644 --- a/libs/ui-components/src/DocumentType/DocumentTypeGenericList.tsx +++ b/libs/ui-components/src/DocumentType/DocumentTypeGenericList.tsx @@ -9,7 +9,6 @@ interface TableOverviewGenericProps { } export function DocumentTypeGenericList({documents, documentType, isLoading}: TableOverviewGenericProps) { - console.info("documents :: ", documents); return (
Date: Tue, 24 Sep 2024 11:44:48 +0700 Subject: [PATCH 12/18] fix title not showing --- apps/cms/src/app/(protected)/content/article/page.tsx | 2 +- .../src/DocumentType/DocumentTypeGenericList.tsx | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/cms/src/app/(protected)/content/article/page.tsx b/apps/cms/src/app/(protected)/content/article/page.tsx index 181155ab..2f94be49 100644 --- a/apps/cms/src/app/(protected)/content/article/page.tsx +++ b/apps/cms/src/app/(protected)/content/article/page.tsx @@ -138,8 +138,8 @@ export default function DocumentTypeDocumentsPage() { }> {documentType ? ( <> + documentType :: {JSON.stringify(documentType)} - {isDialogOpen && ( [ -

{document.data[documentType.titleField] as string}

+

{(document.data.title as string) || ""}

, +

{document.createdAt?.toDate().toUTCString()}

, From 9bb66637f5c51adf72cda220795d6bf8f326e1e8 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Tue, 24 Sep 2024 11:45:00 +0700 Subject: [PATCH 13/18] remove unused --- apps/cms/src/app/(protected)/content/article/page.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/cms/src/app/(protected)/content/article/page.tsx b/apps/cms/src/app/(protected)/content/article/page.tsx index 2f94be49..ae5b9aa9 100644 --- a/apps/cms/src/app/(protected)/content/article/page.tsx +++ b/apps/cms/src/app/(protected)/content/article/page.tsx @@ -138,7 +138,6 @@ export default function DocumentTypeDocumentsPage() { }> {documentType ? ( <> - documentType :: {JSON.stringify(documentType)} {isDialogOpen && ( Date: Tue, 24 Sep 2024 11:57:50 +0700 Subject: [PATCH 14/18] fix title not showing --- .../content/article/[documentId]/page.tsx | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/apps/cms/src/app/(protected)/content/article/[documentId]/page.tsx b/apps/cms/src/app/(protected)/content/article/[documentId]/page.tsx index f842ee0b..0dfec5b5 100644 --- a/apps/cms/src/app/(protected)/content/article/[documentId]/page.tsx +++ b/apps/cms/src/app/(protected)/content/article/[documentId]/page.tsx @@ -27,28 +27,25 @@ export default function DocumentDetailsPage() { }, [documentError, writeError]); useEffect(() => { - if (updateTitleShown) return; - async function onDocumentTitleChange(title: string) { - console.log("[onDocumentTitleChange]", title); - if (!document) { - return; - } - - document.data.title = title; - await update(document); + if (!document) { + return; } - onDocumentTitleChange(title); - }, [document, title, update, updateTitleShown]); - - useEffect(() => { - if (document) { - setTitle(document.data.title as string); - } + setTitle(document.data.title as string); return () => setTitle(""); }, [document]); + async function onDocumentTitleChange(title: string) { + console.log("[onDocumentTitleChange]", title); + if (!document) { + return; + } + + document.data.title = title; + await update(document); + } + async function onDocumentContentChange(content: string) { console.log("[onDocumentContentChange]", content); if (!document) { @@ -78,7 +75,7 @@ export default function DocumentDetailsPage() { placeholder="Title" disabled={readonlyMode} value={title || ""} - onChange={(e) => setTitle(e.target.value)} + onChange={(e) => onDocumentTitleChange(e.target.value)} /> )} From 60744ffab6aed4b2e7ee2ba4bae34b2499afff0c Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Tue, 24 Sep 2024 13:55:49 +0700 Subject: [PATCH 15/18] fix error build --- .../cms/src/app/(protected)/settings/page.tsx | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/apps/cms/src/app/(protected)/settings/page.tsx b/apps/cms/src/app/(protected)/settings/page.tsx index a6f663b8..44263d68 100644 --- a/apps/cms/src/app/(protected)/settings/page.tsx +++ b/apps/cms/src/app/(protected)/settings/page.tsx @@ -2,7 +2,7 @@ import {AcceptFileType, UserNotification} from "@tanam/domain-frontend"; import {CropImage, Modal, Notification, PageHeader} from "@tanam/ui-components"; import Image from "next/image"; -import {useEffect, useState} from "react"; +import {useCallback, useEffect, useState} from "react"; import DarkModeSwitcher from "../../../components/DarkModeSwitcher"; import {Dropzone} from "../../../components/Form/Dropzone"; import {useAuthentication} from "../../../hooks/useAuthentication"; @@ -25,29 +25,19 @@ export default function Settings() { const [afterCropImage, setAfterCropImage] = useState(); const [profilePicture, setProfilePicture] = useState(defaultImage); - useEffect(() => { - if (tanamUser) { - init(); - } - }, [tanamUser, pathUpload]); - - useEffect(() => { - setNotification(userError || storageError); - }, [userError, storageError]); - /** * Initializes component by setting the required state. * Loads the profile picture if upload path is available. * @return {Promise} */ - async function init(): Promise { + const init = useCallback(async () => { if (!pathUpload) { setPathUpload(`tanam-users/${tanamUser?.id}`); return; } await resetChanges(); - } + }, [pathUpload, tanamUser, resetChanges]); /** * Resets upload and crop image-related states. @@ -149,6 +139,18 @@ export default function Settings() { ); + useEffect(() => { + if (!tanamUser) { + return; + } + + init(); + }, [tanamUser, pathUpload, init]); + + useEffect(() => { + setNotification(userError || storageError); + }, [userError, storageError]); + return (
{notification && ( From e8b2f4486ea527d7347d920c157189f4d4276fd4 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Tue, 24 Sep 2024 15:09:01 +0700 Subject: [PATCH 16/18] fix all warning console when build cms --- .../cms/src/app/(protected)/settings/page.tsx | 54 ++++---- apps/cms/src/components/Form/Dropzone.tsx | 7 +- apps/cms/src/components/Sidebar/Sidebar.tsx | 8 +- apps/cms/src/components/SignoutUser.tsx | 10 +- apps/cms/src/components/VoiceRecorder.tsx | 130 +++++++++--------- apps/cms/src/hooks/useAuthentication.tsx | 36 ++--- apps/cms/src/hooks/useFirebaseUi.tsx | 27 ++-- 7 files changed, 141 insertions(+), 131 deletions(-) diff --git a/apps/cms/src/app/(protected)/settings/page.tsx b/apps/cms/src/app/(protected)/settings/page.tsx index 44263d68..cb229e36 100644 --- a/apps/cms/src/app/(protected)/settings/page.tsx +++ b/apps/cms/src/app/(protected)/settings/page.tsx @@ -25,31 +25,6 @@ export default function Settings() { const [afterCropImage, setAfterCropImage] = useState(); const [profilePicture, setProfilePicture] = useState(defaultImage); - /** - * Initializes component by setting the required state. - * Loads the profile picture if upload path is available. - * @return {Promise} - */ - const init = useCallback(async () => { - if (!pathUpload) { - setPathUpload(`tanam-users/${tanamUser?.id}`); - return; - } - - await resetChanges(); - }, [pathUpload, tanamUser, resetChanges]); - - /** - * Resets upload and crop image-related states. - * @return {Promise} - */ - async function resetChanges(): Promise { - setFileUploadContentType(undefined); - resetCropImage(); - - await fetchProfilePicture(); - } - /** * Resets the crop image states. */ @@ -64,12 +39,12 @@ export default function Settings() { * Uses a default image if none is found. * @return {Promise} */ - async function fetchProfilePicture(): Promise { + const fetchProfilePicture = useCallback(async () => { const profilePictureUrl = await getFile(`${pathUpload}/profile.png`); setProfilePicture(profilePictureUrl ?? defaultImage); setBeforeCropImage(profilePicture); - } + }, [getFile, pathUpload, profilePicture]); /** * Handles the submission of the personal information form. @@ -104,6 +79,17 @@ export default function Settings() { } } + /** + * Resets upload and crop image-related states. + * @return {Promise} + */ + const resetChanges = useCallback(async () => { + setFileUploadContentType(undefined); + resetCropImage(); + + await fetchProfilePicture(); + }, [fetchProfilePicture]); + /** * Modal actions for saving or canceling profile picture changes. * @constant @@ -139,6 +125,20 @@ export default function Settings() {
); + /** + * Initializes component by setting the required state. + * Loads the profile picture if upload path is available. + * @return {Promise} + */ + const init = useCallback(async () => { + if (!pathUpload) { + setPathUpload(`tanam-users/${tanamUser?.id}`); + return; + } + + await resetChanges(); + }, [pathUpload, tanamUser, resetChanges]); + useEffect(() => { if (!tanamUser) { return; diff --git a/apps/cms/src/components/Form/Dropzone.tsx b/apps/cms/src/components/Form/Dropzone.tsx index a47d8418..a1b7fba6 100644 --- a/apps/cms/src/components/Form/Dropzone.tsx +++ b/apps/cms/src/components/Form/Dropzone.tsx @@ -11,7 +11,6 @@ export interface DropzoneProps { onChange?: (fileString: string | null, fileBlob: File | null) => void; } -// TODO: DELETE it /** * Dropzone component for handling file uploads and sending data to parent without preview. * @param {DropzoneProps} props - The properties for the dropzone component. @@ -25,9 +24,11 @@ export function Dropzone({value, disabled, accept = AcceptFileType.AllFiles, onC * useEffect hook that resets the file input value when the component unmounts. */ useEffect(() => { + const inputElement = inputRef.current; + return () => { - if (inputRef.current) { - inputRef.current.value = ""; + if (inputElement) { + inputElement.value = ""; } }; }, [value]); diff --git a/apps/cms/src/components/Sidebar/Sidebar.tsx b/apps/cms/src/components/Sidebar/Sidebar.tsx index a76ca20b..8a2ac04c 100644 --- a/apps/cms/src/components/Sidebar/Sidebar.tsx +++ b/apps/cms/src/components/Sidebar/Sidebar.tsx @@ -32,12 +32,14 @@ const Sidebar = ({sidebarOpen, setSidebarOpen}: SidebarProps) => { }; document.addEventListener("keydown", keyHandler); return () => document.removeEventListener("keydown", keyHandler); - }); + }, [sidebarOpen, setSidebarOpen]); // close sidebar when page is changed or window resize useEffect(() => { - setSidebarOpen(false); - }, [pathname, windowSize]); + if (sidebarOpen) { + setSidebarOpen(false); + } + }, [sidebarOpen, pathname, windowSize, setSidebarOpen]); useEffect(() => { // Handler to call on window resize diff --git a/apps/cms/src/components/SignoutUser.tsx b/apps/cms/src/components/SignoutUser.tsx index 56985d99..dcc10298 100644 --- a/apps/cms/src/components/SignoutUser.tsx +++ b/apps/cms/src/components/SignoutUser.tsx @@ -6,12 +6,12 @@ const SignoutUser: React.FC = () => { const {signout} = useAuthentication(); useEffect(() => { - actionSignout(); - }, []); + const actionSignout = () => { + signout(); + }; - function actionSignout() { - signout(); - } + actionSignout(); + }, [signout]); return null; }; diff --git a/apps/cms/src/components/VoiceRecorder.tsx b/apps/cms/src/components/VoiceRecorder.tsx index 0b04d3f6..0cf7917a 100644 --- a/apps/cms/src/components/VoiceRecorder.tsx +++ b/apps/cms/src/components/VoiceRecorder.tsx @@ -1,6 +1,6 @@ "use client"; import {TanamSpeechRecognition} from "@tanam/domain-frontend"; -import {useEffect, useRef, useState} from "react"; +import {useCallback, useEffect, useRef, useState} from "react"; interface VoiceRecorderProps { title?: string; @@ -29,68 +29,9 @@ export default function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { const recognitionRef = useRef(); const streamRef = useRef(null); const audioContextRef = useRef(null); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const peaksInstanceRef = useRef(null); - /** - * Effect to trigger onChange whenever the value prop changes. - */ - useEffect(() => { - if (value) { - onChange(value); - } - }, [value, onChange]); - - /** - * Effect to trigger onLoadingChange whenever the isRecording prop changes. - */ - useEffect(() => { - if (onLoadingChange) { - onLoadingChange(isRecording); - } - }, [isRecording, onLoadingChange]); - - /** - * Effect to trigger initSoundWave whenever the audioUrl changes. - */ - useEffect(() => { - if (audioUrl) { - initSoundWave(); - } - - return () => { - resetSoundWave(); - }; - }, [audioUrl]); - - /** - * Effect hook that sets up the SpeechRecognition instance and its event handlers. - * It handles starting, stopping, and restarting the speech recognition process. - */ - useEffect(() => { - const SpeechRecognition = new TanamSpeechRecognition(); - - if (SpeechRecognition) { - recognitionRef.current = SpeechRecognition; - const recognition = recognitionRef.current; - - if (!recognition) return; - - // Handle the event when speech recognition returns results - recognition.onresult = (event: any) => { - const transcript = Array.from(event.results) - .map((result: any) => result[0]) - .map((result) => result.transcript) - .join(""); - - setTranscript(transcript); // Update local state with the transcript - - if (onTranscriptChange) { - onTranscriptChange(transcript); - } - }; - } - }, [onTranscriptChange, isRecording]); - /** * Starts recording audio using the user"s microphone. * It sets up the MediaRecorder, gathers audio data, and pushes it to the audioChunksRef. @@ -169,7 +110,7 @@ export default function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { /** * Loads the audio buffer and initializes Peak.js with the given options. */ - async function initSoundWave() { + const initSoundWave = useCallback(async () => { resetSoundWave(); // SSR Compatability (https://github.com/bbc/peaks.js/issues/335#issuecomment-682223058) @@ -209,6 +150,7 @@ export default function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { axisGridlineColor: "#ccc", axisLabelColor: "#aaa", randomizeSegmentColor: true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; peaksInstanceRef.current = Peaks.init(options, (err) => { @@ -217,7 +159,7 @@ export default function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { return; } }); - } + }, []); function resetSoundWave() { if (!peaksInstanceRef.current) return; @@ -225,6 +167,68 @@ export default function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { peaksInstanceRef.current.destroy(); } + /** + * Effect to trigger onChange whenever the value prop changes. + */ + useEffect(() => { + if (value) { + onChange(value); + } + }, [value, onChange]); + + /** + * Effect to trigger onLoadingChange whenever the isRecording prop changes. + */ + useEffect(() => { + if (onLoadingChange) { + onLoadingChange(isRecording); + } + }, [isRecording, onLoadingChange]); + + /** + * Effect to trigger initSoundWave whenever the audioUrl changes. + */ + useEffect(() => { + if (audioUrl) { + initSoundWave(); + } + + return () => { + resetSoundWave(); + }; + }, [audioUrl, initSoundWave]); + + /** + * Effect hook that sets up the SpeechRecognition instance and its event handlers. + * It handles starting, stopping, and restarting the speech recognition process. + */ + useEffect(() => { + const SpeechRecognition = new TanamSpeechRecognition(); + + if (SpeechRecognition) { + recognitionRef.current = SpeechRecognition; + const recognition = recognitionRef.current; + + if (!recognition) return; + + // Handle the event when speech recognition returns results + // eslint-disable-next-line @typescript-eslint/no-explicit-any + recognition.onresult = (event: any) => { + const transcript = Array.from(event.results) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .map((result: any) => result[0]) + .map((result) => result.transcript) + .join(""); + + setTranscript(transcript); // Update local state with the transcript + + if (onTranscriptChange) { + onTranscriptChange(transcript); + } + }; + } + }, [onTranscriptChange, isRecording]); + return (
{title &&

{title}

} diff --git a/apps/cms/src/hooks/useAuthentication.tsx b/apps/cms/src/hooks/useAuthentication.tsx index 754dcd49..38c81db8 100644 --- a/apps/cms/src/hooks/useAuthentication.tsx +++ b/apps/cms/src/hooks/useAuthentication.tsx @@ -2,7 +2,7 @@ import {TanamRole} from "@tanam/domain-frontend"; import {User} from "firebase/auth"; import {redirect, usePathname} from "next/navigation"; -import {useEffect, useState} from "react"; +import {useCallback, useEffect, useState} from "react"; import {firebaseAuth} from "../plugins/firebase"; export function useAuthentication() { @@ -13,31 +13,33 @@ export function useAuthentication() { const [userRole, setUserRole] = useState(null); const [isSignedIn, setIsSignedIn] = useState(null); - useEffect(() => { - const unsubscribe = firebaseAuth.onAuthStateChanged((user) => { - console.log("[onAuthStateChanged]", {user}); - setUser(user); - setIsSignedIn(!!user); - fetchUserRole(); - }); - - return () => unsubscribe(); - }, []); - - async function fetchUserRole() { + const fetchUserRole = useCallback(async () => { try { const idTokenResult = await firebaseAuth.currentUser?.getIdTokenResult(); + const role = (idTokenResult?.claims as {tanamRole: TanamRole})?.tanamRole; - setUserRole((idTokenResult?.claims as {tanamRole: TanamRole}).tanamRole); + setUserRole(role || null); - // Redirect when user doesnt have claims - if (pathname !== "/error/insufficient-role" && (userRole === null || !userRole)) { + if (pathname !== "/error/insufficient-role" && !role) { redirect("/error/insufficient-role"); } } catch (error) { setError(error as Error); } - } + }, [pathname]); + + useEffect(() => { + const unsubscribe = firebaseAuth.onAuthStateChanged((user) => { + console.log("[onAuthStateChanged]", {user}); + + setUser(user); + setIsSignedIn(!!user); + + if (user) fetchUserRole(); + }); + + return () => unsubscribe(); + }, [fetchUserRole]); async function signout() { console.log("[signout]"); diff --git a/apps/cms/src/hooks/useFirebaseUi.tsx b/apps/cms/src/hooks/useFirebaseUi.tsx index c44ed3ec..59d1d261 100644 --- a/apps/cms/src/hooks/useFirebaseUi.tsx +++ b/apps/cms/src/hooks/useFirebaseUi.tsx @@ -2,7 +2,7 @@ import {AuthCredential, GoogleAuthProvider} from "firebase/auth"; import {auth as firebaseAuthUi} from "firebaseui"; import "firebaseui/dist/firebaseui.css"; -import {useEffect, useState} from "react"; +import {useCallback, useEffect, useState} from "react"; import {firebaseAuth} from "../plugins/firebase"; const firebaseUi = firebaseAuthUi.AuthUI.getInstance() || new firebaseAuthUi.AuthUI(firebaseAuth); @@ -11,17 +11,8 @@ export function useFirebaseUi() { const [isSignUp, setIsSignup] = useState(false); const [isLoading, setIsLoading] = useState(false); - useEffect(() => { - renderFirebaseUi(); - - return () => { - setIsLoading(false); - setIsSignup(false); - }; - }, []); - - function renderFirebaseUi() { - if (!window || typeof window === "undefined") return; + const renderFirebaseUi = useCallback(() => { + if (typeof window === "undefined") return; const selector = "#firebaseuiAuthContainer"; @@ -50,7 +41,17 @@ export function useFirebaseUi() { }, }, }); - } + }, [isSignUp]); + + useEffect(() => { + renderFirebaseUi(); + + return () => { + firebaseUi.reset(); + setIsLoading(false); + setIsSignup(false); + }; + }, [renderFirebaseUi]); return { isLoading, From 347dd49ff30887a1e64357484687abb10fa061c5 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Tue, 24 Sep 2024 15:20:58 +0700 Subject: [PATCH 17/18] missing google gen ai api key in env --- .github/workflows/configure_apphosting.yml | 2 ++ apphosting.yaml | 3 +++ 2 files changed, 5 insertions(+) diff --git a/.github/workflows/configure_apphosting.yml b/.github/workflows/configure_apphosting.yml index 5a051929..351ad945 100644 --- a/.github/workflows/configure_apphosting.yml +++ b/.github/workflows/configure_apphosting.yml @@ -31,6 +31,8 @@ jobs: variable: NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID - secret: tanamAppId variable: NEXT_PUBLIC_FIREBASE_APP_ID + - secret: tanamGenAiApiKey + variable: GOOGLE_GENAI_API_KEY steps: - uses: actions/checkout@v4 diff --git a/apphosting.yaml b/apphosting.yaml index 3b11dc17..8544c6cf 100644 --- a/apphosting.yaml +++ b/apphosting.yaml @@ -23,3 +23,6 @@ env: - variable: NEXT_PUBLIC_FIREBASE_APP_ID secret: tanamAppId + + - variable: GOOGLE_GENAI_API_KEY + secret: tanamGenAiApiKey From b03b9cd889b3f5120dd1232f38a92f5d7dfdb7d5 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Wed, 25 Sep 2024 10:34:40 +0700 Subject: [PATCH 18/18] put back to the original --- .../src/DocumentType/DocumentTypeGenericList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/ui-components/src/DocumentType/DocumentTypeGenericList.tsx b/libs/ui-components/src/DocumentType/DocumentTypeGenericList.tsx index f86f4b0d..b0de58e1 100644 --- a/libs/ui-components/src/DocumentType/DocumentTypeGenericList.tsx +++ b/libs/ui-components/src/DocumentType/DocumentTypeGenericList.tsx @@ -8,14 +8,14 @@ interface TableOverviewGenericProps { isLoading?: boolean; } -export function DocumentTypeGenericList({documents, isLoading}: TableOverviewGenericProps) { +export function DocumentTypeGenericList({documents, documentType, isLoading}: TableOverviewGenericProps) { return (
[ -

{(document.data.title as string) || ""}

+

{document.data[documentType.titleField] as string}

,