diff --git a/bun.lockb b/bun.lockb index 789ed7b..4a6ab78 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index a175536..4e2102d 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@t3-oss/env-nextjs": "^0.11.0", "@tanstack/eslint-plugin-query": "^5.43.1", "@tanstack/react-query": "^5.45.0", - "@tanstack/react-query-devtools": "^5.50.1", + "@tanstack/react-query-devtools": "^5.51.23", "@tanstack/react-table": "^8.19.2", "@vercel/postgres": "^0.9.0", "class-variance-authority": "^0.7.0", diff --git a/src/app/@modal/(.)paperless/document/[id]/page.tsx b/src/app/@modal/(.)paperless/document/[id]/page.tsx deleted file mode 100644 index 6215b79..0000000 --- a/src/app/@modal/(.)paperless/document/[id]/page.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import DocumentPreview from "@/components/document-preview"; -import { Modal } from "@/components/modal"; - -export default function ModalDocumentPage({ - params, -}: { - params: { id: number }; -}) { - return ( - -
-
-
- -
-
-
-
- ); -} diff --git a/src/app/_analytics/provider.tsx b/src/app/_analytics/provider.tsx deleted file mode 100644 index 13dcf18..0000000 --- a/src/app/_analytics/provider.tsx +++ /dev/null @@ -1,38 +0,0 @@ -"use client"; -import { useAuth, useUser } from "@clerk/nextjs"; -import posthog from "posthog-js"; -import { PostHogProvider } from "posthog-js/react"; -import { useEffect } from "react"; - -if (typeof window !== "undefined") { - posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, { - api_host: "/ingest", - ui_host: "https://app.posthog.com", - }); -} - -export function CSPostHogProvider({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ); -} - -function PostHogAuthWrapper({ children }: { children: React.ReactNode }) { - const auth = useAuth(); - const userInfo = useUser(); - - useEffect(() => { - if (userInfo.user) { - posthog.identify(userInfo.user.id, { - email: userInfo.user.emailAddresses[0]?.emailAddress, - name: userInfo.user.fullName, - }); - } else if (!auth.isSignedIn) { - posthog.reset(); - } - }, [auth, userInfo]); - - return children; -} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index d17459a..17fd712 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -6,7 +6,6 @@ import { ClerkProvider } from "@clerk/nextjs"; import { ThemeProvider } from "@/components/theme-provider"; import { cn } from "@/lib/utils"; import { dark } from "@clerk/themes"; -import { CSPostHogProvider } from "@/app/_analytics/provider"; export const metadata = { title: "Homelab Connector", @@ -27,7 +26,6 @@ export default function RootLayout({ baseTheme: dark, }} > - - ); } diff --git a/src/app/paperless/details/[id]/page.tsx b/src/app/paperless/details/[id]/page.tsx deleted file mode 100644 index 652a10e..0000000 --- a/src/app/paperless/details/[id]/page.tsx +++ /dev/null @@ -1,230 +0,0 @@ -"use client"; - -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { toast } from "sonner"; -import { getUserData } from "@/app/actions"; -import { buttonVariants } from "@/components/ui/button"; -import { useRouter, useSearchParams } from "next/navigation"; -import { - useQuery, - QueryClientProvider, - QueryClient, -} from "@tanstack/react-query"; -import type { UsersTableType } from "@/server/db/schema"; -import type { PaperlessDocumentType } from "@/types"; -import React from "react"; -import BodyMessage from "@/components/body-message"; -import Link from "next/link"; -import LoadingSpinner from "@/components/loading-spinner"; -import OpenExternalLink from "@/components/external-link"; - -const queryClient = new QueryClient(); - -export async function getPaperlessDocument( - documentId: number, - userData: UsersTableType, -): Promise { - try { - const url = `${userData.paperlessURL}/api/documents/${documentId}/download/`; - const response = await fetch(url, { - headers: { - Authorization: `Token ${userData.paperlessToken}`, - }, - }); - if (response.ok) { - const blob = await response.blob(); - const objectUrl = URL.createObjectURL(blob); - return objectUrl; - } else { - console.error("Failed to fetch PDF"); - return null; - } - } catch (error) { - console.error("Error fetching PDF:", error); - return null; - } -} - -async function deleteDocument(documentId: number) { - const userData = await getUserData(); - if (!userData) { - throw new Error("User data not found"); - } - const body = { - documents: [documentId], - method: "delete", - }; - const response = await fetch( - `${userData.paperlessURL}/api/documents/bulk_edit/ `, - { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Token ${userData.paperlessToken}`, - }, - body: JSON.stringify(body), - }, - ); - return response; -} - -const fetchUserData = async (): Promise => { - const response = await fetch(`/api/getUserData`); - if (!response.ok) { - throw new Error("Network error"); - } - const data = (await response.json()) as UsersTableType; - return data; -}; - -async function getPaperlessDocumentData(id: number, userData: UsersTableType) { - try { - const url = `${userData.paperlessURL}/api/documents/${id}/?truncate_content=true`; - const response = await fetch(url, { - headers: { - Authorization: `Token ${userData.paperlessToken}`, - }, - }); - if (response.ok) { - const data = (await response.json()) as PaperlessDocumentType; - return data; - } else { - console.error("Failed to fetch PD dataF"); - return null; - } - } catch (error) { - console.error("Error fetching PDF data:", error); - return null; - } -} - -function DocumentDetailsInner(props: { id: number }) { - const router = useRouter(); - const searchParams = useSearchParams(); - const query = searchParams.get("query"); - - const { data: userData, isLoading: isUserDataLoading } = useQuery({ - queryKey: ["userData"], - queryFn: fetchUserData, - staleTime: 24 * 60 * 60 * 1000, // 1 day in milliseconds - refetchOnWindowFocus: false, - }); - - const { data: pdfUrl, isLoading: isPdfUrlLoading } = useQuery({ - queryKey: ["pdfUrl", props.id, userData], // Include id and userData in the query key - queryFn: async () => { - const result = await getPaperlessDocument(props.id, userData!); - return result; - }, - enabled: !!userData, - refetchOnWindowFocus: false, - }); - - const { data: documentData, isLoading: isdocumentDataLoading } = useQuery({ - queryKey: ["documentData", props.id, userData], // Include id and userData in the query key - queryFn: async () => { - const result = await getPaperlessDocumentData(props.id, userData!); - return result; - }, - enabled: !!userData, - refetchOnWindowFocus: false, - }); - - if (isUserDataLoading || isdocumentDataLoading || isPdfUrlLoading) { - return Loading...; - } else if (!userData || !documentData || !pdfUrl) { - return Error; - } - return ( -
-
-
-
-

{documentData?.title}

-
- - - - -
- - Back to search - - - Open - - - - Delete - - - - Are you absolutely sure? - - This action cannot be undone. This will permanently delete - the pdf. - - - - Cancel - { - const response = await deleteDocument(props.id); - if (response.ok) { - toast("Pdf deleted", { - description: "The pdf has been deleted.", - }); - } else { - toast("Error deleting pdf", { - description: - "An error occurred while deleting the pdf.", - }); - } - router.push(`/paperless?query=${query}`); - }} - > - Continue - - - - -
- - - - ); -} - -export default function DocumentDetails({ - params, -}: { - params: { id: number }; -}) { - return ( - - - - ); -} diff --git a/src/app/paperless/document/[id]/page.tsx b/src/app/paperless/document/[id]/page.tsx index 71a025d..652a10e 100644 --- a/src/app/paperless/document/[id]/page.tsx +++ b/src/app/paperless/document/[id]/page.tsx @@ -1,19 +1,230 @@ "use client"; -import DocumentPreview from "@/components/document-preview"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { toast } from "sonner"; +import { getUserData } from "@/app/actions"; +import { buttonVariants } from "@/components/ui/button"; +import { useRouter, useSearchParams } from "next/navigation"; +import { + useQuery, + QueryClientProvider, + QueryClient, +} from "@tanstack/react-query"; +import type { UsersTableType } from "@/server/db/schema"; +import type { PaperlessDocumentType } from "@/types"; +import React from "react"; +import BodyMessage from "@/components/body-message"; +import Link from "next/link"; +import LoadingSpinner from "@/components/loading-spinner"; +import OpenExternalLink from "@/components/external-link"; -export default function FullPageDocumentPage({ +const queryClient = new QueryClient(); + +export async function getPaperlessDocument( + documentId: number, + userData: UsersTableType, +): Promise { + try { + const url = `${userData.paperlessURL}/api/documents/${documentId}/download/`; + const response = await fetch(url, { + headers: { + Authorization: `Token ${userData.paperlessToken}`, + }, + }); + if (response.ok) { + const blob = await response.blob(); + const objectUrl = URL.createObjectURL(blob); + return objectUrl; + } else { + console.error("Failed to fetch PDF"); + return null; + } + } catch (error) { + console.error("Error fetching PDF:", error); + return null; + } +} + +async function deleteDocument(documentId: number) { + const userData = await getUserData(); + if (!userData) { + throw new Error("User data not found"); + } + const body = { + documents: [documentId], + method: "delete", + }; + const response = await fetch( + `${userData.paperlessURL}/api/documents/bulk_edit/ `, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Token ${userData.paperlessToken}`, + }, + body: JSON.stringify(body), + }, + ); + return response; +} + +const fetchUserData = async (): Promise => { + const response = await fetch(`/api/getUserData`); + if (!response.ok) { + throw new Error("Network error"); + } + const data = (await response.json()) as UsersTableType; + return data; +}; + +async function getPaperlessDocumentData(id: number, userData: UsersTableType) { + try { + const url = `${userData.paperlessURL}/api/documents/${id}/?truncate_content=true`; + const response = await fetch(url, { + headers: { + Authorization: `Token ${userData.paperlessToken}`, + }, + }); + if (response.ok) { + const data = (await response.json()) as PaperlessDocumentType; + return data; + } else { + console.error("Failed to fetch PD dataF"); + return null; + } + } catch (error) { + console.error("Error fetching PDF data:", error); + return null; + } +} + +function DocumentDetailsInner(props: { id: number }) { + const router = useRouter(); + const searchParams = useSearchParams(); + const query = searchParams.get("query"); + + const { data: userData, isLoading: isUserDataLoading } = useQuery({ + queryKey: ["userData"], + queryFn: fetchUserData, + staleTime: 24 * 60 * 60 * 1000, // 1 day in milliseconds + refetchOnWindowFocus: false, + }); + + const { data: pdfUrl, isLoading: isPdfUrlLoading } = useQuery({ + queryKey: ["pdfUrl", props.id, userData], // Include id and userData in the query key + queryFn: async () => { + const result = await getPaperlessDocument(props.id, userData!); + return result; + }, + enabled: !!userData, + refetchOnWindowFocus: false, + }); + + const { data: documentData, isLoading: isdocumentDataLoading } = useQuery({ + queryKey: ["documentData", props.id, userData], // Include id and userData in the query key + queryFn: async () => { + const result = await getPaperlessDocumentData(props.id, userData!); + return result; + }, + enabled: !!userData, + refetchOnWindowFocus: false, + }); + + if (isUserDataLoading || isdocumentDataLoading || isPdfUrlLoading) { + return Loading...; + } else if (!userData || !documentData || !pdfUrl) { + return Error; + } + return ( +
+
+
+
+

{documentData?.title}

+
+ + + + +
+ + Back to search + + + Open + + + + Delete + + + + Are you absolutely sure? + + This action cannot be undone. This will permanently delete + the pdf. + + + + Cancel + { + const response = await deleteDocument(props.id); + if (response.ok) { + toast("Pdf deleted", { + description: "The pdf has been deleted.", + }); + } else { + toast("Error deleting pdf", { + description: + "An error occurred while deleting the pdf.", + }); + } + router.push(`/paperless?query=${query}`); + }} + > + Continue + + + + +
+ + + + ); +} + +export default function DocumentDetails({ params, }: { params: { id: number }; }) { return ( -
-
-
- -
-
-
+ + + ); } diff --git a/src/app/paperless/page.tsx b/src/app/paperless/page.tsx index 6477644..6a08ba5 100644 --- a/src/app/paperless/page.tsx +++ b/src/app/paperless/page.tsx @@ -15,7 +15,7 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { zodResolver } from "@hookform/resolvers/zod"; -import { useCallback } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useQuery, QueryClientProvider, @@ -26,6 +26,8 @@ import { getUserData } from "@/app/actions"; import Link from "next/link"; import OpenInternalLink from "@/components/internal-link"; import type { PaperlessDocumentsType } from "@/types"; +import type { UsersTableType } from "@/server/db/schema"; +import Image from "next/image"; const queryClient = new QueryClient(); @@ -50,6 +52,30 @@ async function getPaperlessDocuments(query: string) { return data; } +export async function getPaperlessThumbnail( + documentId: number, + userData: UsersTableType, +): Promise { + try { + const url = `${userData.paperlessURL}/api/documents/${documentId}/thumb/`; + const response = await fetch(url, { + headers: { + Authorization: `Token ${userData.paperlessToken}`, + }, + }); + if (response.ok) { + const blob = await response.blob(); + const objectUrl = URL.createObjectURL(blob); + return objectUrl; + } else { + console.error("Failed to fetch PDF"); + return null; + } + } catch (error) { + console.error("Error fetching PDF:", error); + return null; + } +} function DocumentsSearch() { const formSchema = z.object({ @@ -109,7 +135,11 @@ function DocumentsPage() { const searchParams = useSearchParams(); const query = searchParams.get("query"); - const PaperlessDocuments = useQuery({ + const { + data: PaperlessDocuments, + isLoading: isLoadingDocuments, + error: documentsError, + } = useQuery({ queryKey: ["key", query], queryFn: async () => { if (!query) { @@ -118,13 +148,16 @@ function DocumentsPage() { const response = await getPaperlessDocuments(query); return response; }, - // This ensures the query does not run if there's no query string enabled: !!query, staleTime: 60 * 1000, // 1 minute in milliseconds refetchOnWindowFocus: false, }); - const userData = useQuery({ + const { + data: userData, + isLoading: isLoadingUserData, + error: userDataError, + } = useQuery({ queryKey: ["userData"], queryFn: async () => { const data = await getUserData(); @@ -134,20 +167,39 @@ function DocumentsPage() { refetchOnWindowFocus: false, }); + const [imageUrls, setImageUrls] = useState(new Map()); + + useEffect(() => { + const fetchImageUrls = async () => { + const newImageUrls = new Map(); + if (PaperlessDocuments?.results && userData) { + for (const document of PaperlessDocuments.results) { + const url = await getPaperlessThumbnail(document.id, userData); + newImageUrls.set(document.id, url); + } + } + setImageUrls(newImageUrls); + }; + + void fetchImageUrls(); + }, [PaperlessDocuments, userData]); + if (!query) { - return

Start Searching!

; + return ( +

Start searching

+ ); } - if (PaperlessDocuments.isLoading || userData.isLoading) { + if (isLoadingDocuments || isLoadingUserData) { return Loading...; - } else if (!userData.data?.paperlessURL) { + } else if (userDataError || !userData?.paperlessURL) { return (

You need to set your paperless url in settings

); - } else if (!PaperlessDocuments.data || PaperlessDocuments.error) { + } else if (documentsError || !PaperlessDocuments) { return (

Connection failed! Check that the paperless url/token is set correctly @@ -156,27 +208,37 @@ function DocumentsPage() { ); } - const paperlessDocumentMap = PaperlessDocuments.data.results; + const paperlessDocumentMap = PaperlessDocuments.results; - if (!paperlessDocumentMap ?? paperlessDocumentMap.length === 0) { - return

No results!

; + if (paperlessDocumentMap.length === 0) { + return

No results

; } return ( <> -

Search Results

-
    +

    Search Results

    +
    {paperlessDocumentMap.map((document, index) => ( -
  • +
    + {document.title} {document.title} -
  • +
    ))} -
+ ); } diff --git a/src/components/document-preview.tsx b/src/components/document-preview.tsx deleted file mode 100644 index 78a169e..0000000 --- a/src/components/document-preview.tsx +++ /dev/null @@ -1,107 +0,0 @@ -"use client"; - -import { - useQuery, - QueryClientProvider, - QueryClient, -} from "@tanstack/react-query"; -import type { UsersTableType } from "@/server/db/schema"; -import { Button, buttonVariants } from "@/components/ui/button"; -import { ChevronLeft } from "lucide-react"; -import { useRouter } from "next/navigation"; -import { useSearchParams } from "next/navigation"; - -const queryClient = new QueryClient(); - -export async function getPaperlessThumbnail( - documentId: number, - userData: UsersTableType, -): Promise { - try { - const url = `${userData.paperlessURL}/api/documents/${documentId}/thumb/`; - const response = await fetch(url, { - headers: { - Authorization: `Token ${userData.paperlessToken}`, - }, - }); - if (response.ok) { - const blob = await response.blob(); - const objectUrl = URL.createObjectURL(blob); - return objectUrl; - } else { - console.error("Failed to fetch PDF"); - return null; - } - } catch (error) { - console.error("Error fetching PDF:", error); - return null; - } -} - -function SkeletonLoader() { - return ( -
-
- {/* Pulsing Background */} -
-
-
- ); -} - -const fetchUserData = async (): Promise => { - const response = await fetch(`/api/getUserData`); - if (!response.ok) { - throw new Error("Network error"); - } - const data = (await response.json()) as UsersTableType; - return data; -}; - -function Preview(props: { id: number }) { - const { data: userData, isLoading: isUserDataLoading } = useQuery({ - queryKey: ["userData"], - queryFn: fetchUserData, - staleTime: 24 * 60 * 60 * 1000, // 1 day in milliseconds - refetchOnWindowFocus: false, - }); - - const { data: pdfUrl, isLoading: isPdfUrlLoading } = useQuery({ - queryKey: ["pdfUrl", props.id, userData], // Include id and paperlessURL in the query key - queryFn: async () => { - console.log("fetching"); - return await getPaperlessThumbnail(props.id, userData!); - }, - enabled: !!userData, - refetchOnWindowFocus: false, - }); - - if (isPdfUrlLoading ?? isUserDataLoading) { - return ; - } - - return Document Preview; -} - -export default function DocumentPreview(props: { id: number }) { - const router = useRouter(); - const searchParams = useSearchParams(); - const query = searchParams.get("query"); - - return ( - - -
- - - Open full page - -
-
- ); -}