From 6fe61cd5e3b185730038399d87440a99f8abf7a1 Mon Sep 17 00:00:00 2001 From: Sara Date: Wed, 6 Dec 2023 17:10:48 +0100 Subject: [PATCH 1/9] fix transcript delete --- server/reflector/db/transcripts.py | 5 ++++- server/tests/test_transcripts_audio_download.py | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/server/reflector/db/transcripts.py b/server/reflector/db/transcripts.py index 78e34a1c..310f97e3 100644 --- a/server/reflector/db/transcripts.py +++ b/server/reflector/db/transcripts.py @@ -1,4 +1,5 @@ import json +import os from contextlib import asynccontextmanager from datetime import datetime from pathlib import Path @@ -188,7 +189,9 @@ class Transcript(BaseModel): return [participant.model_dump(mode=mode) for participant in self.participants] def unlink(self): - self.data_path.unlink(missing_ok=True) + for filename in os.listdir(self.data_path): + if os.path.isfile(os.path.join("directory_path", filename)): + os.remove(os.path.join("directory_path", filename)) @property def data_path(self): diff --git a/server/tests/test_transcripts_audio_download.py b/server/tests/test_transcripts_audio_download.py index 28f83fff..efedeb59 100644 --- a/server/tests/test_transcripts_audio_download.py +++ b/server/tests/test_transcripts_audio_download.py @@ -118,3 +118,16 @@ async def test_transcript_audio_download_range_with_seek( assert response.status_code == 206 assert response.headers["content-type"] == content_type assert response.headers["content-range"].startswith("bytes 100-") + + +@pytest.mark.asyncio +async def test_transcript_delete_with_audio(fake_transcript): + from reflector.app import app + + ac = AsyncClient(app=app, base_url="http://test/v1") + response = await ac.delete(f"/transcripts/{fake_transcript.id}") + assert response.status_code == 200 + assert response.json()["status"] == "ok" + + response = await ac.get(f"/transcripts/{fake_transcript.id}") + assert response.status_code == 404 From 5ef7dc0a76791f25c6223f826b781f44c84cf746 Mon Sep 17 00:00:00 2001 From: Sara Date: Wed, 6 Dec 2023 17:12:10 +0100 Subject: [PATCH 2/9] improve list view --- www/app/[domain]/browse/page.tsx | 217 +++++++++++++----- www/app/[domain]/browse/pagination.tsx | 11 +- .../[domain]/transcripts/createTranscript.ts | 9 +- .../[domain]/transcripts/useTranscriptList.ts | 14 +- 4 files changed, 180 insertions(+), 71 deletions(-) diff --git a/www/app/[domain]/browse/page.tsx b/www/app/[domain]/browse/page.tsx index 0a180143..7c7f3e06 100644 --- a/www/app/[domain]/browse/page.tsx +++ b/www/app/[domain]/browse/page.tsx @@ -6,81 +6,176 @@ import { Title } from "../../lib/textComponents"; import Pagination from "./pagination"; import Link from "next/link"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faGear } from "@fortawesome/free-solid-svg-icons"; +import { + faCheck, + faGear, + faMicrophone, + faStar, + faTrash, + faX, +} from "@fortawesome/free-solid-svg-icons"; import useTranscriptList from "../transcripts/useTranscriptList"; +import { formatTime } from "../../lib/time"; +import getApi from "../../api"; +import { useError } from "../../(errors)/errorContext"; export default function TranscriptBrowser() { const [page, setPage] = useState(1); - const { loading, response } = useTranscriptList(page); + const { loading, response, refetch } = useTranscriptList(page); + const [transcriptToDeleteId, setTranscriptToDeleteId] = useState(""); + const [deletionLoading, setDeletionLoading] = useState(false); + const [deletedItems, setDeletedItems] = useState([]); + const api = getApi(); + const { setError } = useError(); - return ( -
-
- Past transcripts - +
+ ); - {loading && ( -
- + No transcripts found, but you can  + + record a meeting + +  to get started. +
+ ); + + const handleDelete = + (id: string) => (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + setTranscriptToDeleteId(id); + }; + + const deleteTranscript = () => (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + if (!deletionLoading) { + api + ?.v1TranscriptDelete({ transcriptId: transcriptToDeleteId }) + .then(() => { + setDeletionLoading(false); + setDeletedItems([...deletedItems, transcriptToDeleteId]); + setTranscriptToDeleteId(""); + refetch(); + }) + .catch((err) => { + setDeletionLoading(false); + setError(err, "There was an error deleting the transcript"); + }); + } + }; + const cancelDelete = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + setTranscriptToDeleteId(""); + }; + + return ( +
+
+
+ Past transcripts +
- )} - {!loading && !response && ( -
- No transcripts found, but you can  - - record a meeting - -  to get started. -
- )} -
-
- {response?.items.map((item: GetTranscript) => ( -
-
-
- - {item.title || item.name} - +
+ {response?.items + .filter((item) => !deletedItems.includes(item.id)) + .map((item: GetTranscript) => ( + +
+
+

+ {item.title || item.name} +

- {item.locked ? ( -
- Locked -
- ) : ( - <> - )} + {item.locked && ( +
+ Locked +
+ )} - {item.source_language ? ( -
- {item.source_language} + {item.status == "ended" && ( + + )} + {item.status == "error" && ( + + )} + {item.status == "idle" && ( + + )} + {item.status == "processing" && ( + + )} + {item.status == "recording" && ( + + )} + + {item.sourceLanguage && ( +
+ {item.sourceLanguage} +
+ )} +
+
+
+ {new Date(item.createdAt).toLocaleDateString("en-US")} + {"\u00A0"}-{"\u00A0"} + {formatTime(Math.floor(item.duration))} +
{item.shortSummary}
- ) : ( - <> - )} + {item.status !== "ended" && ( + + )} + +

Are you sure you want to delete {item.title} ?

+

This action is not reversible.

+ + +
+
-
- {new Date(item.created_at).toLocaleDateString("en-US")} -
-
{item.short_summary}
-
-
- ))} + + ))}
diff --git a/www/app/[domain]/browse/pagination.tsx b/www/app/[domain]/browse/pagination.tsx index e10d5321..721d1747 100644 --- a/www/app/[domain]/browse/pagination.tsx +++ b/www/app/[domain]/browse/pagination.tsx @@ -1,3 +1,6 @@ +import { faArrowLeft, faArrowRight } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + type PaginationProps = { page: number; setPage: (page: number) => void; @@ -40,13 +43,13 @@ export default function Pagination(props: PaginationProps) { return (
{pageNumbers.map((pageNumber) => ( @@ -62,13 +65,13 @@ export default function Pagination(props: PaginationProps) { ))}
); diff --git a/www/app/[domain]/transcripts/createTranscript.ts b/www/app/[domain]/transcripts/createTranscript.ts index 8435e6c2..6ad6ad6e 100644 --- a/www/app/[domain]/transcripts/createTranscript.ts +++ b/www/app/[domain]/transcripts/createTranscript.ts @@ -1,16 +1,17 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; + import { useError } from "../../(errors)/errorContext"; -import { GetTranscript, CreateTranscript } from "../../api"; +import { CreateTranscript } from "../../api"; import useApi from "../../lib/useApi"; -type UseTranscript = { +type UseCreateTranscript = { transcript: GetTranscript | null; loading: boolean; error: Error | null; create: (transcriptCreationDetails: CreateTranscript) => void; }; -const useCreateTranscript = (): UseTranscript => { +const useCreateTranscript = (): UseCreateTranscript => { const [transcript, setTranscript] = useState(null); const [loading, setLoading] = useState(false); const [error, setErrorState] = useState(null); diff --git a/www/app/[domain]/transcripts/useTranscriptList.ts b/www/app/[domain]/transcripts/useTranscriptList.ts index 7621a9f8..15e17372 100644 --- a/www/app/[domain]/transcripts/useTranscriptList.ts +++ b/www/app/[domain]/transcripts/useTranscriptList.ts @@ -7,6 +7,7 @@ type TranscriptList = { response: Page_GetTranscript_ | null; loading: boolean; error: Error | null; + refetch: () => void; }; //always protected @@ -16,6 +17,15 @@ const useTranscriptList = (page: number): TranscriptList => { const [error, setErrorState] = useState(null); const { setError } = useError(); const api = useApi(); + const [refetchCount, setRefetchCount] = useState(0); + + const refetch = () => { + setRefetchCount(refetchCount + 1); + }; + + useEffect(() => { + setResponse(null); + }, [page]); useEffect(() => { if (!api) return; @@ -32,9 +42,9 @@ const useTranscriptList = (page: number): TranscriptList => { setError(err); setErrorState(err); }); - }, [!api, page]); + }, [!api, page, refetchCount]); - return { response, loading, error }; + return { response, loading, error, refetch }; }; export default useTranscriptList; From 1116a0e484ee9c9ef9e1f5408c08378c66178a5a Mon Sep 17 00:00:00 2001 From: Sara Date: Fri, 22 Dec 2023 13:19:08 +0100 Subject: [PATCH 3/9] fix folder deletion --- server/reflector/db/transcripts.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/reflector/db/transcripts.py b/server/reflector/db/transcripts.py index 310f97e3..e25de7a1 100644 --- a/server/reflector/db/transcripts.py +++ b/server/reflector/db/transcripts.py @@ -1,5 +1,6 @@ import json import os +import shutil from contextlib import asynccontextmanager from datetime import datetime from pathlib import Path @@ -189,9 +190,8 @@ class Transcript(BaseModel): return [participant.model_dump(mode=mode) for participant in self.participants] def unlink(self): - for filename in os.listdir(self.data_path): - if os.path.isfile(os.path.join("directory_path", filename)): - os.remove(os.path.join("directory_path", filename)) + if os.path.exists(self.data_path) and os.path.isdir(self.data_path): + shutil.rmtree(self.data_path) @property def data_path(self): From 1c4600b65e23bdbde3d36cdb907ad9ad2b596761 Mon Sep 17 00:00:00 2001 From: Sara Date: Sat, 13 Jan 2024 18:35:31 +0100 Subject: [PATCH 4/9] whoopsie rebasing --- www/app/[domain]/transcripts/createTranscript.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/app/[domain]/transcripts/createTranscript.ts b/www/app/[domain]/transcripts/createTranscript.ts index 6ad6ad6e..cf68498c 100644 --- a/www/app/[domain]/transcripts/createTranscript.ts +++ b/www/app/[domain]/transcripts/createTranscript.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { useError } from "../../(errors)/errorContext"; -import { CreateTranscript } from "../../api"; +import { CreateTranscript, GetTranscript } from "../../api"; import useApi from "../../lib/useApi"; type UseCreateTranscript = { From 23b52a6d1fa9e075a0766a4efbed65971d758d40 Mon Sep 17 00:00:00 2001 From: Sara Date: Mon, 15 Jan 2024 15:54:02 +0100 Subject: [PATCH 5/9] change browse page to new design --- www/app/[domain]/browse/page.tsx | 281 ++++++++++-------- www/app/[domain]/layout.tsx | 29 +- .../[domain]/transcripts/useTranscriptList.ts | 4 - www/yarn.lock | 5 + 4 files changed, 178 insertions(+), 141 deletions(-) diff --git a/www/app/[domain]/browse/page.tsx b/www/app/[domain]/browse/page.tsx index 7c7f3e06..6303f1a0 100644 --- a/www/app/[domain]/browse/page.tsx +++ b/www/app/[domain]/browse/page.tsx @@ -2,70 +2,83 @@ import React, { useState } from "react"; import { GetTranscript } from "../../api"; -import { Title } from "../../lib/textComponents"; import Pagination from "./pagination"; import Link from "next/link"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faCheck, - faGear, - faMicrophone, - faStar, - faTrash, - faX, -} from "@fortawesome/free-solid-svg-icons"; +import { FaCheck, FaTrash, FaStar, FaMicrophone } from "react-icons/fa"; +import { MdError } from "react-icons/md"; import useTranscriptList from "../transcripts/useTranscriptList"; import { formatTime } from "../../lib/time"; -import getApi from "../../api"; +import useApi from "../../lib/useApi"; import { useError } from "../../(errors)/errorContext"; +import { + Flex, + Spinner, + Heading, + Button, + Card, + CardBody, + CardFooter, + Stack, + Text, + Icon, + Grid, + Divider, + Popover, + PopoverTrigger, + PopoverContent, + PopoverArrow, + PopoverCloseButton, + PopoverHeader, + PopoverBody, + PopoverFooter, +} from "@chakra-ui/react"; +import { PlusSquareIcon } from "@chakra-ui/icons"; +// import { useFiefUserinfo } from "@fief/fief/nextjs/react"; export default function TranscriptBrowser() { const [page, setPage] = useState(1); const { loading, response, refetch } = useTranscriptList(page); - const [transcriptToDeleteId, setTranscriptToDeleteId] = useState(""); const [deletionLoading, setDeletionLoading] = useState(false); const [deletedItems, setDeletedItems] = useState([]); - const api = getApi(); + const api = useApi(); const { setError } = useError(); + // Todo: fief add name field to userinfo + // const user = useFiefUserinfo(); + // console.log(user); + if (loading && !response) return ( -
- -
+ + + ); if (!loading && !response) return ( -
- No transcripts found, but you can  - - record a meeting - -  to get started. -
+ + + No transcripts found, but you can  + + record a meeting + +  to get started. + + ); - const handleDelete = - (id: string) => (e: React.MouseEvent) => { - e.stopPropagation(); - e.preventDefault(); - setTranscriptToDeleteId(id); - }; - - const deleteTranscript = () => (e: React.MouseEvent) => { + const prevent = (e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); + }; + + const handleDeleteTranscript = (transcriptToDeleteId) => (e) => { if (!deletionLoading) { api - ?.v1TranscriptDelete({ transcriptId: transcriptToDeleteId }) + ?.v1TranscriptDelete(transcriptToDeleteId) .then(() => { setDeletionLoading(false); setDeletedItems([...deletedItems, transcriptToDeleteId]); - setTranscriptToDeleteId(""); refetch(); }) .catch((err) => { @@ -74,110 +87,118 @@ export default function TranscriptBrowser() { }); } }; - const cancelDelete = (e: React.MouseEvent) => { - e.stopPropagation(); - e.preventDefault(); - setTranscriptToDeleteId(""); - }; return ( -
-
-
- Past transcripts + + + {/* {user?.fields?.name}'s Meetings */} + Your Meetings + + {loading && } + -
-
- {response?.items - .filter((item) => !deletedItems.includes(item.id)) - .map((item: GetTranscript) => ( - -
-
-

- {item.title || item.name} -

- {item.locked && ( -
- Locked -
- )} + + + - {item.status == "ended" && ( - - )} - {item.status == "error" && ( - - )} - {item.status == "idle" && ( - - )} - {item.status == "processing" && ( - - )} - {item.status == "recording" && ( - - )} + + {response?.items + .filter((item) => !deletedItems.includes(item.id)) + .map((item: GetTranscript) => ( + + + + {item.title || item.name || "Unamed Transcript"} - {item.sourceLanguage && ( -
- {item.sourceLanguage} -
- )} -
-
-
- {new Date(item.createdAt).toLocaleDateString("en-US")} - {"\u00A0"}-{"\u00A0"} - {formatTime(Math.floor(item.duration))} -
{item.shortSummary}
-
- {item.status !== "ended" && ( - - )} - -

Are you sure you want to delete {item.title} ?

-

This action is not reversible.

- - -
-
-
- - ))} -
-
-
+ {item.status == "ended" && ( + + )} + {item.status == "error" && ( + + )} + {item.status == "idle" && ( + + )} + {item.status == "processing" && ( + + )} + {item.status == "recording" && ( + + )} + + + + {new Date(item.created_at).toLocaleString("en-US")} + {"\u00A0"}-{"\u00A0"} + {formatTime(Math.floor(item.duration / 1000))} + + {item.short_summary} + + + + {item.status !== "ended" && ( + <> + + + + + + + + + + + Are you sure you want to delete {item.title} ? + + + This action is not reversible. + + + + + + + + + )} + + ))} + + ); } diff --git a/www/app/[domain]/layout.tsx b/www/app/[domain]/layout.tsx index cce65fc3..95d0f544 100644 --- a/www/app/[domain]/layout.tsx +++ b/www/app/[domain]/layout.tsx @@ -6,7 +6,6 @@ import UserInfo from "../(auth)/userInfo"; import { ErrorProvider } from "../(errors)/errorContext"; import ErrorMessage from "../(errors)/errorMessage"; import Image from "next/image"; -import Link from "next/link"; import About from "../(aboutAndPrivacy)/about"; import Privacy from "../(aboutAndPrivacy)/privacy"; import { DomainContextProvider } from "./domainContext"; @@ -15,6 +14,8 @@ import { ErrorBoundary } from "@sentry/nextjs"; import { cookies } from "next/dist/client/components/headers"; import { SESSION_COOKIE_NAME } from "../lib/fief"; import { Providers } from "../providers"; +import NextLink from "next/link"; +import { Container, Flex, Link } from "@chakra-ui/react"; const poppins = Poppins({ subsets: ["latin"], weight: ["200", "400", "600"] }); @@ -91,13 +92,25 @@ export default async function RootLayout({ children, params }: LayoutProps) { -
-
+ {/* Logo on the left */} @@ -120,6 +133,7 @@ export default async function RootLayout({ children, params }: LayoutProps) {
{/* Text link on the right */} @@ -130,6 +144,7 @@ export default async function RootLayout({ children, params }: LayoutProps) {  ยท  @@ -158,10 +173,10 @@ export default async function RootLayout({ children, params }: LayoutProps) { <> )}
-
+ {children} -
+
diff --git a/www/app/[domain]/transcripts/useTranscriptList.ts b/www/app/[domain]/transcripts/useTranscriptList.ts index 15e17372..2cfab374 100644 --- a/www/app/[domain]/transcripts/useTranscriptList.ts +++ b/www/app/[domain]/transcripts/useTranscriptList.ts @@ -23,10 +23,6 @@ const useTranscriptList = (page: number): TranscriptList => { setRefetchCount(refetchCount + 1); }; - useEffect(() => { - setResponse(null); - }, [page]); - useEffect(() => { if (!api) return; setLoading(true); diff --git a/www/yarn.lock b/www/yarn.lock index b2d55ac1..baac24ec 100644 --- a/www/yarn.lock +++ b/www/yarn.lock @@ -4674,6 +4674,11 @@ react-focus-lock@^2.9.4: use-callback-ref "^1.3.0" use-sidecar "^1.1.2" +react-icons@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.0.1.tgz#1694e11bfa2a2888cab47dcc30154ce90485feee" + integrity sha512-WqLZJ4bLzlhmsvme6iFdgO8gfZP17rfjYEJ2m9RsZjZ+cc4k1hTzknEz63YS1MeT50kVzoa1Nz36f4BEx+Wigw== + react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" From 113938b9ebe2e67b01ccc2bb52577d1538177e7e Mon Sep 17 00:00:00 2001 From: Sara Date: Mon, 15 Jan 2024 17:15:35 +0100 Subject: [PATCH 6/9] adapt pagination and fixes deletion --- www/app/[domain]/browse/page.tsx | 146 ++++++++++++------------- www/app/[domain]/browse/pagination.tsx | 49 +++++---- www/app/providers.tsx | 3 +- www/app/theme.ts | 34 ++++++ 4 files changed, 132 insertions(+), 100 deletions(-) create mode 100644 www/app/theme.ts diff --git a/www/app/[domain]/browse/page.tsx b/www/app/[domain]/browse/page.tsx index 6303f1a0..443c50ff 100644 --- a/www/app/[domain]/browse/page.tsx +++ b/www/app/[domain]/browse/page.tsx @@ -4,6 +4,7 @@ import React, { useState } from "react"; import { GetTranscript } from "../../api"; import Pagination from "./pagination"; import Link from "next/link"; +import { FaGear } from "react-icons/fa6"; import { FaCheck, FaTrash, FaStar, FaMicrophone } from "react-icons/fa"; import { MdError } from "react-icons/md"; import useTranscriptList from "../transcripts/useTranscriptList"; @@ -31,6 +32,7 @@ import { PopoverHeader, PopoverBody, PopoverFooter, + IconButton, } from "@chakra-ui/react"; import { PlusSquareIcon } from "@chakra-ui/icons"; // import { useFiefUserinfo } from "@fief/fief/nextjs/react"; @@ -39,7 +41,6 @@ export default function TranscriptBrowser() { const [page, setPage] = useState(1); const { loading, response, refetch } = useTranscriptList(page); const [deletionLoading, setDeletionLoading] = useState(false); - const [deletedItems, setDeletedItems] = useState([]); const api = useApi(); const { setError } = useError(); @@ -67,18 +68,14 @@ export default function TranscriptBrowser() { ); - const prevent = (e: React.MouseEvent) => { - e.stopPropagation(); - e.preventDefault(); - }; - const handleDeleteTranscript = (transcriptToDeleteId) => (e) => { - if (!deletionLoading) { + e.stopPropagation(); + if (api && !deletionLoading) { + setDeletionLoading(true); api - ?.v1TranscriptDelete(transcriptToDeleteId) + .v1TranscriptDelete(transcriptToDeleteId) .then(() => { setDeletionLoading(false); - setDeletedItems([...deletedItems, transcriptToDeleteId]); refetch(); }) .catch((err) => { @@ -101,7 +98,7 @@ export default function TranscriptBrowser() { {/* {user?.fields?.name}'s Meetings */} Your Meetings - {loading && } + {loading || (deletionLoading && )} - {response?.items - .filter((item) => !deletedItems.includes(item.id)) - .map((item: GetTranscript) => ( - - - - {item.title || item.name || "Unamed Transcript"} + {response?.items.map((item: GetTranscript) => ( + + + + {item.title || item.name || "Unamed Transcript"} - {item.status == "ended" && ( - - )} - {item.status == "error" && ( - - )} - {item.status == "idle" && ( - - )} - {item.status == "processing" && ( - - )} - {item.status == "recording" && ( - - )} - - - - {new Date(item.created_at).toLocaleString("en-US")} - {"\u00A0"}-{"\u00A0"} - {formatTime(Math.floor(item.duration / 1000))} - - {item.short_summary} - - + {item.status == "ended" && ( + + )} + {item.status == "error" && ( + + )} + {item.status == "idle" && ( + + )} + {item.status == "processing" && ( + + )} + {item.status == "recording" && ( + + )} + + + + {new Date(item.created_at).toLocaleString("en-US")} + {"\u00A0"}-{"\u00A0"} + {formatTime(Math.floor(item.duration / 1000))} + + {item.short_summary} + + - {item.status !== "ended" && ( - <> - - - - - - - - - - - Are you sure you want to delete {item.title} ? - - - This action is not reversible. - - - - - - - - - )} - - ))} + + + + + + )} + + ))} ); diff --git a/www/app/[domain]/browse/pagination.tsx b/www/app/[domain]/browse/pagination.tsx index 721d1747..7c67a60b 100644 --- a/www/app/[domain]/browse/pagination.tsx +++ b/www/app/[domain]/browse/pagination.tsx @@ -1,5 +1,5 @@ -import { faArrowLeft, faArrowRight } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Button, Flex, IconButton } from "@chakra-ui/react"; +import { FaChevronLeft, FaChevronRight } from "react-icons/fa"; type PaginationProps = { page: number; @@ -39,40 +39,41 @@ export default function Pagination(props: PaginationProps) { setPage(newPage); } }; - return ( -
- + aria-label="Previous page" + /> {pageNumbers.map((pageNumber) => ( - + ))} - -
+ aria-label="Next page" + /> + ); } diff --git a/www/app/providers.tsx b/www/app/providers.tsx index 5197ff15..e1c4f283 100644 --- a/www/app/providers.tsx +++ b/www/app/providers.tsx @@ -1,7 +1,8 @@ "use client"; import { ChakraProvider } from "@chakra-ui/react"; +import theme from "./theme"; export function Providers({ children }: { children: React.ReactNode }) { - return {children}; + return {children}; } diff --git a/www/app/theme.ts b/www/app/theme.ts new file mode 100644 index 00000000..e951eef0 --- /dev/null +++ b/www/app/theme.ts @@ -0,0 +1,34 @@ +// 1. Import `extendTheme` +import { extendTheme } from "@chakra-ui/react"; + +// 2. Call `extendTheme` and pass your custom values +const theme = extendTheme({ + colors: { + blue: { + primary: "#3158E2", + 500: "#3158E2", + light: "#B1CBFF", + 200: "#B1CBFF", + dark: "#0E1B48", + 900: "#0E1B48", + }, + red: { + primary: "#DF7070", + 500: "#DF7070", + light: "#FBD5D5", + 200: "#FBD5D5", + }, + gray: { + bg: "#F4F4F4", + 100: "#F4F4F4", + light: "#D5D5D5", + 200: "#D5D5D5", + primary: "#838383", + 500: "#838383", + }, + light: "#FFFFFF", + dark: "#0C0D0E", + }, +}); + +export default theme; From 1e6615dad3b7f1d7fe6602d97c60a5ef1a2756bc Mon Sep 17 00:00:00 2001 From: Sara Date: Mon, 15 Jan 2024 17:24:36 +0100 Subject: [PATCH 7/9] refine card style --- www/app/[domain]/browse/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/app/[domain]/browse/page.tsx b/www/app/[domain]/browse/page.tsx index 443c50ff..560b8c42 100644 --- a/www/app/[domain]/browse/page.tsx +++ b/www/app/[domain]/browse/page.tsx @@ -128,7 +128,7 @@ export default function TranscriptBrowser() { mb="4" > {response?.items.map((item: GetTranscript) => ( - + {item.title || item.name || "Unamed Transcript"} From 8924067a45c0ff6379134e6ab1f4dfd95ddcedc8 Mon Sep 17 00:00:00 2001 From: Sara Date: Mon, 15 Jan 2024 17:30:14 +0100 Subject: [PATCH 8/9] fix package --- www/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/www/package.json b/www/package.json index b0821b2f..8c86456e 100644 --- a/www/package.json +++ b/www/package.json @@ -38,6 +38,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-dropdown": "^1.11.0", + "react-icons": "^5.0.1", "react-markdown": "^9.0.0", "react-qr-code": "^2.0.12", "react-select-search": "^4.1.7", From 20681b081041154ca32871ff4724636843f9812c Mon Sep 17 00:00:00 2001 From: Sara Date: Thu, 25 Jan 2024 13:51:22 +0100 Subject: [PATCH 9/9] Improvements based on feedback --- www/app/[domain]/browse/page.tsx | 248 +++++++++++++++++++++---------- www/app/lib/expandableText.tsx | 46 ++++++ 2 files changed, 214 insertions(+), 80 deletions(-) create mode 100644 www/app/lib/expandableText.tsx diff --git a/www/app/[domain]/browse/page.tsx b/www/app/[domain]/browse/page.tsx index 560b8c42..211c927b 100644 --- a/www/app/[domain]/browse/page.tsx +++ b/www/app/[domain]/browse/page.tsx @@ -1,9 +1,9 @@ "use client"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { GetTranscript } from "../../api"; import Pagination from "./pagination"; -import Link from "next/link"; +import NextLink from "next/link"; import { FaGear } from "react-icons/fa6"; import { FaCheck, FaTrash, FaStar, FaMicrophone } from "react-icons/fa"; import { MdError } from "react-icons/md"; @@ -11,12 +11,14 @@ import useTranscriptList from "../transcripts/useTranscriptList"; import { formatTime } from "../../lib/time"; import useApi from "../../lib/useApi"; import { useError } from "../../(errors)/errorContext"; +import { FaEllipsisVertical } from "react-icons/fa6"; import { Flex, Spinner, Heading, Button, Card, + Link, CardBody, CardFooter, Stack, @@ -33,8 +35,22 @@ import { PopoverBody, PopoverFooter, IconButton, + Spacer, + Menu, + MenuButton, + MenuItem, + MenuList, + AlertDialog, + AlertDialogOverlay, + AlertDialogContent, + AlertDialogHeader, + AlertDialogBody, + AlertDialogFooter, + keyframes, + Tooltip, } from "@chakra-ui/react"; import { PlusSquareIcon } from "@chakra-ui/icons"; +import { ExpandableText } from "../../lib/expandableText"; // import { useFiefUserinfo } from "@fief/fief/nextjs/react"; export default function TranscriptBrowser() { @@ -43,11 +59,19 @@ export default function TranscriptBrowser() { const [deletionLoading, setDeletionLoading] = useState(false); const api = useApi(); const { setError } = useError(); + const cancelRef = React.useRef(null); + const [transcriptToDeleteId, setTranscriptToDeleteId] = + React.useState(); + const [deletedItemIds, setDeletedItemIds] = React.useState(); // Todo: fief add name field to userinfo // const user = useFiefUserinfo(); // console.log(user); + useEffect(() => { + setDeletedItemIds([]); + }, [page, response]); + if (loading && !response) return ( @@ -67,6 +91,7 @@ export default function TranscriptBrowser() { ); + const onCloseDeletion = () => setTranscriptToDeleteId(undefined); const handleDeleteTranscript = (transcriptToDeleteId) => (e) => { e.stopPropagation(); @@ -77,6 +102,11 @@ export default function TranscriptBrowser() { .then(() => { setDeletionLoading(false); refetch(); + onCloseDeletion(); + setDeletedItemIds((deletedItemIds) => [ + deletedItemIds, + ...transcriptToDeleteId, + ]); }) .catch((err) => { setDeletionLoading(false); @@ -94,23 +124,27 @@ export default function TranscriptBrowser() { overflowY="scroll" maxH="100%" > - + {/* {user?.fields?.name}'s Meetings */} Your Meetings - - {loading || (deletionLoading && )} + {loading || (deletionLoading && )} - + + - - + - {response?.items.map((item: GetTranscript) => ( - - - - {item.title || item.name || "Unamed Transcript"} + {response?.items + .filter((i) => !deletedItemIds?.includes(i.id)) + .map((item: GetTranscript) => ( + + + + {item.status == "ended" && ( + + + + + + )} + {item.status == "error" && ( + + + + + + )} + {item.status == "idle" && ( + + + + + + )} + {item.status == "processing" && ( + + + + + + )} + {item.status == "recording" && ( + + + + + + )} + + + {item.title || item.name || "Unamed Transcript"} + + - {item.status == "ended" && ( - - )} - {item.status == "error" && ( - - )} - {item.status == "idle" && ( - - )} - {item.status == "processing" && ( - - )} - {item.status == "recording" && ( - - )} - - - - {new Date(item.created_at).toLocaleString("en-US")} - {"\u00A0"}-{"\u00A0"} - {formatTime(Math.floor(item.duration / 1000))} - - {item.short_summary} - - - - {item.status !== "ended" && ( - <> - - - - - + + } + aria-label="actions" + /> + + } - aria-label="Delete" - /> - - - - - - Are you sure you want to delete {item.title} ? - - This action is not reversible. - - - - - - - - )} - - ))} + onClick={() => setTranscriptToDeleteId(item.id)} + icon={} + > + Delete + + + + + + Delete{" "} + {item.title || item.name || "Unamed Transcript"} + + + + Are you sure? You can't undo this action + afterwards. + + + + + + + + + + + + + + + {new Date(item.created_at).toLocaleString("en-US")} + {"\u00A0"}-{"\u00A0"} + {formatTime(Math.floor(item.duration / 1000))} + + + {item.short_summary} + + + + + ))} ); diff --git a/www/app/lib/expandableText.tsx b/www/app/lib/expandableText.tsx new file mode 100644 index 00000000..1925d1f7 --- /dev/null +++ b/www/app/lib/expandableText.tsx @@ -0,0 +1,46 @@ +import type { BoxProps } from "@chakra-ui/react"; +import { Box, Button, Text } from "@chakra-ui/react"; +import React, { forwardRef, useState } from "react"; + +interface Props extends BoxProps { + children: React.ReactNode; + noOfLines: number; +} + +export const ExpandableText = forwardRef( + ({ children, noOfLines, ...rest }, ref) => { + const [expandedCount, setExpandedCount] = useState( + noOfLines, + ); + const [isClicked, setIsClicked] = useState(false); + const handleToggle = () => { + setIsClicked(true); + setExpandedCount(expandedCount ? undefined : noOfLines); + }; + + const inputRef = React.useRef(null); + + const isTextClamped = + (inputRef.current?.scrollHeight as number) > + (inputRef.current?.clientHeight as number) || isClicked; + + return ( + + + {children} + + + + ); + }, +); + +ExpandableText.displayName = "ExpandableText";