mirror of
https://github.com/marcogll/TaxHacker_s23.git
synced 2026-01-13 13:25:18 +00:00
(squash) init
feat: filters, settings, backups fix: ts compile errors feat: new dashboard, webp previews and settings feat: use webp for pdfs feat: use webp fix: analyze resets old data fix: switch to corsproxy fix: switch to free cors fix: max upload limit fix: currency conversion feat: transaction export fix: currency conversion feat: refactor settings actions feat: new loader feat: README + LICENSE doc: update readme doc: update readme doc: update readme doc: update screenshots ci: bump prisma
This commit is contained in:
53
components/files/preview.tsx
Normal file
53
components/files/preview.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
"use client"
|
||||
|
||||
import { File } from "@prisma/client"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { useState } from "react"
|
||||
|
||||
export function FilePreview({ file }: { file: File }) {
|
||||
const [isEnlarged, setIsEnlarged] = useState(false)
|
||||
|
||||
const fileSize =
|
||||
file.metadata && typeof file.metadata === "object" && "size" in file.metadata
|
||||
? Number(file.metadata.size) / 1024 / 1024
|
||||
: 0
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-2 p-4 overflow-hidden">
|
||||
<div className="aspect-[3/4]">
|
||||
<Image
|
||||
src={`/files/preview/${file.id}`}
|
||||
alt={file.filename}
|
||||
width={300}
|
||||
height={400}
|
||||
className={`${
|
||||
isEnlarged
|
||||
? "fixed inset-0 z-50 m-auto w-screen h-screen object-contain cursor-zoom-out"
|
||||
: "w-full h-full object-contain cursor-zoom-in"
|
||||
}`}
|
||||
onClick={() => setIsEnlarged(!isEnlarged)}
|
||||
/>
|
||||
{isEnlarged && (
|
||||
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm z-40" onClick={() => setIsEnlarged(false)} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 mt-2 overflow-hidden">
|
||||
<h2 className="text-md underline font-semibold overflow-ellipsis">
|
||||
<Link href={`/files/download/${file.id}`}>{file.filename}</Link>
|
||||
</h2>
|
||||
<p className="text-sm overflow-ellipsis">
|
||||
<strong>Type:</strong> {file.mimetype}
|
||||
</p>
|
||||
<p className="text-sm">
|
||||
<strong>Size:</strong> {fileSize < 1 ? (fileSize * 1024).toFixed(2) + " KB" : fileSize.toFixed(2) + " MB"}
|
||||
</p>
|
||||
<p className="text-xs overflow-ellipsis">
|
||||
<strong>Path:</strong> {file.path}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
134
components/files/screen-drop-area.tsx
Normal file
134
components/files/screen-drop-area.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
"use client"
|
||||
|
||||
import { useNotification } from "@/app/context"
|
||||
import { uploadFilesAction } from "@/app/files/actions"
|
||||
import { AlertCircle, CloudUpload, Loader2 } from "lucide-react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { startTransition, useEffect, useRef, useState } from "react"
|
||||
|
||||
export default function ScreenDropArea({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
const { showNotification } = useNotification()
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const [isUploading, setIsUploading] = useState(false)
|
||||
const [uploadError, setUploadError] = useState("")
|
||||
const dragCounter = useRef(0)
|
||||
|
||||
const handleDragEnter = (e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
dragCounter.current++
|
||||
|
||||
if (dragCounter.current === 1) {
|
||||
setIsDragging(true)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
dragCounter.current--
|
||||
|
||||
if (dragCounter.current === 0) {
|
||||
setIsDragging(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDrop = async (e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
// Reset counter and dragging state
|
||||
dragCounter.current = 0
|
||||
setIsDragging(false)
|
||||
setIsUploading(true)
|
||||
setUploadError("")
|
||||
|
||||
const files = e.dataTransfer.files
|
||||
if (files && files.length > 0) {
|
||||
try {
|
||||
const formData = new FormData()
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
formData.append("files", files[i])
|
||||
}
|
||||
|
||||
startTransition(async () => {
|
||||
const result = await uploadFilesAction(null, formData)
|
||||
if (result.success) {
|
||||
showNotification({ code: "sidebar.unsorted", message: "new" })
|
||||
setTimeout(() => showNotification({ code: "sidebar.unsorted", message: "" }), 3000)
|
||||
router.push("/unsorted")
|
||||
} else {
|
||||
setUploadError(result.error ? result.error : "Something went wrong...")
|
||||
}
|
||||
setIsUploading(false)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Upload error:", error)
|
||||
setIsUploading(false)
|
||||
setUploadError(error instanceof Error ? error.message : "Something went wrong...")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add event listeners to document body
|
||||
useEffect(() => {
|
||||
document.body.addEventListener("dragenter", handleDragEnter as any)
|
||||
document.body.addEventListener("dragover", handleDragOver as any)
|
||||
document.body.addEventListener("dragleave", handleDragLeave as any)
|
||||
document.body.addEventListener("drop", handleDrop as any)
|
||||
|
||||
return () => {
|
||||
document.body.removeEventListener("dragenter", handleDragEnter as any)
|
||||
document.body.removeEventListener("dragover", handleDragOver as any)
|
||||
document.body.removeEventListener("dragleave", handleDragLeave as any)
|
||||
document.body.removeEventListener("drop", handleDrop as any)
|
||||
}
|
||||
}, [isDragging])
|
||||
|
||||
return (
|
||||
<div className="relative min-h-screen w-full">
|
||||
{children}
|
||||
|
||||
{isDragging && (
|
||||
<div
|
||||
className="fixed inset-0 bg-opacity-20 backdrop-blur-sm z-50 flex items-center justify-center"
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
>
|
||||
<div className="bg-white dark:bg-gray-800 p-8 rounded-lg shadow-xl text-center">
|
||||
<CloudUpload className="h-16 w-16 mx-auto mb-4 text-primary" />
|
||||
<h3 className="text-xl font-semibold mb-2">Drop Files to Upload</h3>
|
||||
<p className="text-gray-600 dark:text-gray-400">Drop anywhere on the screen</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isUploading && (
|
||||
<div className="fixed inset-0 bg-opacity-20 backdrop-blur-sm z-50 flex items-center justify-center">
|
||||
<div className="bg-white dark:bg-gray-800 p-8 rounded-lg shadow-xl text-center">
|
||||
<Loader2 className="h-16 w-16 mx-auto mb-4 text-primary animate-spin" />
|
||||
<h3 className="text-xl font-semibold mb-2">Uploading...</h3>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{uploadError && (
|
||||
<div className="fixed inset-0 bg-opacity-20 backdrop-blur-sm z-50 flex items-center justify-center">
|
||||
<div className="bg-white dark:bg-gray-800 p-8 rounded-lg shadow-xl text-center">
|
||||
<AlertCircle className="h-16 w-16 mx-auto mb-4 text-red-500" />
|
||||
<h3 className="text-xl font-semibold mb-2">Upload Error</h3>
|
||||
<p className="text-gray-600 dark:text-gray-400">{uploadError}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
75
components/files/upload-button.tsx
Normal file
75
components/files/upload-button.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
"use client"
|
||||
|
||||
import { useNotification } from "@/app/context"
|
||||
import { uploadFilesAction } from "@/app/files/actions"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { FILE_ACCEPTED_MIMETYPES } from "@/lib/files"
|
||||
import { Loader2 } from "lucide-react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { ComponentProps, startTransition, useRef, useState } from "react"
|
||||
|
||||
export function UploadButton({ children, ...props }: { children: React.ReactNode } & ComponentProps<typeof Button>) {
|
||||
const router = useRouter()
|
||||
const { showNotification } = useNotification()
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const [uploadError, setUploadError] = useState("")
|
||||
const [isUploading, setIsUploading] = useState(false)
|
||||
|
||||
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setUploadError("")
|
||||
setIsUploading(true)
|
||||
if (e.target.files && e.target.files.length > 0) {
|
||||
const formData = new FormData()
|
||||
|
||||
// Append all selected files to the FormData
|
||||
for (let i = 0; i < e.target.files.length; i++) {
|
||||
formData.append("files", e.target.files[i])
|
||||
}
|
||||
|
||||
// Submit the files using the server action
|
||||
startTransition(async () => {
|
||||
const result = await uploadFilesAction(null, formData)
|
||||
if (result.success) {
|
||||
showNotification({ code: "sidebar.unsorted", message: "new" })
|
||||
setTimeout(() => showNotification({ code: "sidebar.unsorted", message: "" }), 3000)
|
||||
router.push("/unsorted")
|
||||
} else {
|
||||
setUploadError(result.error ? result.error : "Something went wrong...")
|
||||
}
|
||||
setIsUploading(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleButtonClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault() // Prevent any form submission
|
||||
fileInputRef.current?.click()
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
id="fileInput"
|
||||
className="hidden"
|
||||
multiple
|
||||
accept={FILE_ACCEPTED_MIMETYPES}
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
|
||||
<Button onClick={handleButtonClick} disabled={isUploading} type="button" {...props}>
|
||||
{isUploading ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Uploading...
|
||||
</>
|
||||
) : (
|
||||
<>{children}</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{uploadError && <span className="text-red-500">⚠️ {uploadError}</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user