Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: paperless documents modal #38

Merged
merged 21 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions empty-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default {};
15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,22 @@
"db:migrate": "drizzle-kit migrate",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio",
"dev": "next dev --turbo",
"dev": "next dev",
"lint": "next lint",
"start": "next start"
},
"dependencies": {
"@clerk/nextjs": "^5.1.5",
"@clerk/themes": "^2.1.9",
"@clerk/types": "^4.6.1",
"@hookform/resolvers": "^3.6.0",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.5",
"@radix-ui/react-tooltip": "^1.1.1",
"@react-pdf-viewer/core": "^3.12.0",
"@react-pdf/renderer": "^3.4.4",
"@t3-oss/env-nextjs": "^0.10.1",
"@tanstack/eslint-plugin-query": "^5.43.1",
"@tanstack/react-query": "^5.45.0",
Expand All @@ -41,7 +40,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.51.5",
"server-only": "^0.0.1",
"react-pdf": "^9.0.0",
"sonner": "^1.5.0",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
Expand Down Expand Up @@ -69,6 +68,10 @@
},
"packageManager": "[email protected]",
"trustedDependencies": [
"@clerk/shared"
"@clerk/shared",
"bufferutil",
"canvas",
"esbuild",
"utf-8-validate"
]
}
31 changes: 31 additions & 0 deletions src/app/@modal/(.)paperless/document/[id]/modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"use client";

import { type ElementRef, useEffect, useRef } from "react";
import { useRouter } from "next/navigation";
import { createPortal } from "react-dom";

export function Modal({ children }: { children: React.ReactNode }) {
const router = useRouter();
const dialogRef = useRef<ElementRef<"dialog">>(null);

useEffect(() => {
if (!dialogRef.current?.open) {
dialogRef.current?.showModal();
}
}, []);

function onDismiss() {
router.back();
}

return createPortal(
<dialog
ref={dialogRef}
className="absolute h-screen w-screen bg-zinc-500 bg-opacity-20"
onClose={onDismiss}
>
<div className="h-full p-6 md:p-20">{children}</div>
</dialog>,
document.getElementById("modal-root")!,
);
}
14 changes: 14 additions & 0 deletions src/app/@modal/(.)paperless/document/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import DocumentViewer from "@/components/document-viewer";
import { Modal } from "./modal";

export default function FullPageDocumentPage({
params,
}: {
params: { id: number };
}) {
return (
<Modal>
<DocumentViewer id={params.id} />
</Modal>
);
}
3 changes: 3 additions & 0 deletions src/app/@modal/default.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function DefaultModal() {
return null;
}
4 changes: 0 additions & 4 deletions src/app/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@ export async function getUserData() {
}

export async function getPaperlessDocuments(query: string) {
const { userId } = auth();

if (!userId) return null;

const userData = await getUserData();

if (!query || query == "null" || query.length < 3 || !userData) return null;
Expand Down
10 changes: 7 additions & 3 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ export const metadata = {

export default function RootLayout({
children,
modal,
}: {
children: React.ReactNode;
modal: React.ReactNode;
}) {
return (
<ClerkProvider
Expand All @@ -32,14 +34,16 @@ export default function RootLayout({
GeistSans.variable,
)}
>
<body className="">
<body className="h-screen">
<ThemeProvider attribute="class" defaultTheme="dark">
<div className="flex flex-col gap-12 md:gap-0">
<div className="flex h-full flex-col gap-12 md:gap-0">
<TopNav />
<div className="flex flex-col items-center justify-center p-4 px-6">
<div className="flex h-full flex-col items-center p-4 px-6">
{children}
</div>
</div>
{modal}
<div id="modal-root" />
</ThemeProvider>
</body>
</html>
Expand Down
2 changes: 1 addition & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import OpenLinkInNewPage from "@/components/open-link-in-new-page";

export default async function HomePage() {
export default function HomePage() {
return (
<main>
<div>
Expand Down
9 changes: 9 additions & 0 deletions src/app/paperless/document/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import DocumentViewer from "@/components/document-viewer";

export default function ModalDocumentPage({
params,
}: {
params: { id: number };
}) {
return <DocumentViewer id={params.id} />;
}
16 changes: 5 additions & 11 deletions src/app/paperless/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@ import {
import { Input } from "@/components/ui/input";
import { zodResolver } from "@hookform/resolvers/zod";
import { useCallback } from "react";
import { ExternalLink } from "lucide-react";
import {
useQuery,
QueryClientProvider,
QueryClient,
} from "@tanstack/react-query";
import LoadingSpinner from "@/components/loading-spinner";
import { getPaperlessDocuments, getUserData } from "../actions";
import { getPaperlessDocuments, getUserData } from "@/app/actions";
import Link from "next/link";

const queryClient = new QueryClient();
Expand Down Expand Up @@ -129,8 +128,6 @@ function DocumentsPage() {
);
}

const paperlessURL = userData.data?.paperlessURL;

const paperlessDocumentMap = PaperlessDocuments.data.results;

if (paperlessDocumentMap.length === 0) {
Expand All @@ -143,15 +140,12 @@ function DocumentsPage() {
<ul className="list-disc">
{paperlessDocumentMap.map((document, index) => (
<li className="underline" key={index}>
<a
rel="noopener noreferrer"
target="_blank"
className="text-blue-600 underline hover:text-blue-800"
href={`${paperlessURL}/api/documents/${document.id}/preview/#search="${query}"`}
<Link
className="underline hover:text-slate-300"
href={`/paperless/document/${document.id}?query=${query}`}
>
{document.title}
<ExternalLink size={16} className="mx-1 inline-block" />
</a>
</Link>
</li>
))}
</ul>
Expand Down
2 changes: 1 addition & 1 deletion src/app/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { useState } from "react";
import { useUser } from "@clerk/nextjs";
import { redirect, usePathname } from "next/navigation";
import LoadingSpinner from "@/components/loading-spinner";
import { getUserData, setUserProperty } from "../actions";
import { getUserData, setUserProperty } from "@/app/actions";
import { Toaster } from "@/components/ui/sonner";
import { toast } from "sonner";
import {
Expand Down
113 changes: 113 additions & 0 deletions src/components/document-viewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"use client";

import { useState, useEffect } from "react";
import LoadingSpinner from "@/components/loading-spinner";
import { Button } from "./ui/button";
import { useRouter } from "next/navigation";
import { getUserData } from "@/app/actions";

export async function getPaperlessDocument(
documentId: number,
): Promise<string | null> {
const userData = await getUserData();
if (!userData) {
console.error("Error getting user data");
return null;
}
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;
}
}

export default function DocumentViewer(props: { id: number }) {
const router = useRouter();

const [pdfUrl, setPdfUrl] = useState<string | null>(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
const fetchData = async () => {
setLoading(true);

try {
const objectUrl = await getPaperlessDocument(props.id);
if (objectUrl) {
setPdfUrl(objectUrl);
} else {
setPdfUrl(null);
}
} catch (error) {
console.error("An error occurred:", error);
setPdfUrl(null);
} finally {
setLoading(false);
}
};
fetchData().catch((error) => {
console.error("An error occurred:", error);
});
}, [props.id]); // Dependency array to refetch if id changes

if (loading) {
return (
<div className="flex justify-center">
<div className="mx-auto max-w-sm rounded-lg bg-black p-4 shadow-md">
<LoadingSpinner className="w-min rounded-full text-xl font-bold">
Loading...
</LoadingSpinner>
</div>
</div>
);
}

if (!pdfUrl) {
return <h1>Failed to get document</h1>;
}

return (
<div className="flex h-full w-full min-w-0 justify-center">
<div className="flex h-4/5 flex-col rounded-xl bg-slate-600/50 md:w-1/2">
<div className="m-4 flex flex-grow flex-col justify-center gap-8 md:m-8 md:flex-row md:gap-16">
<div className="h-full flex-shrink flex-grow">
<object
data={pdfUrl}
type="application/pdf"
width="100%"
height="100%"
>
<p>
Your web browser doesn&apos;t have a PDF plugin. Instead you can
<a href={pdfUrl}>click here to download the PDF file.</a>
</p>
</object>
</div>
<div className="flex flex-shrink-0 flex-col">
<Button
onClick={() => {
router.back();
}}
>
Back
</Button>
</div>
</div>
</div>
</div>
);
}
11 changes: 8 additions & 3 deletions src/components/loading-spinner.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { cn } from "@/lib/utils";
import { LoaderCircle } from "lucide-react";

interface LoadingSpinnerProps {
children: React.ReactNode;
children?: React.ReactNode;
className?: string;
}

export default function LoadingSpinner({ children }: LoadingSpinnerProps) {
export default function LoadingSpinner({
children,
className,
}: LoadingSpinnerProps) {
return (
<div className="flex flex-row place-content-center gap-1">
<div className={cn("flex flex-row place-content-center gap-1", className)}>
<LoaderCircle className="animate-spin" />
{children}
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/components/topnav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export function TopNav() {
{/* Desktop links */}
<div className="hidden h-full items-center rounded-r bg-slate-300 dark:bg-slate-700 md:flex">
<Link
href="paperless"
href="/paperless"
className={buttonVariants({ variant: "link" })}
>
Paperless-ngx
Expand Down Expand Up @@ -153,7 +153,7 @@ export function TopNav() {
<DropdownMenuLabel>Links</DropdownMenuLabel>
<DropdownMenuItem asChild>
<Link
href="paperless"
href="/paperless"
className={buttonVariants({ variant: "link" })}
>
Paperless-ngx
Expand Down
Loading
Loading