diff --git a/src/app/groups/[groupId]/expenses/create-from-receipt-button.tsx b/src/app/groups/[groupId]/expenses/create-from-receipt-button.tsx index c157cc9..dab04a1 100644 --- a/src/app/groups/[groupId]/expenses/create-from-receipt-button.tsx +++ b/src/app/groups/[groupId]/expenses/create-from-receipt-button.tsx @@ -26,7 +26,7 @@ import { import { ToastAction } from '@/components/ui/toast' import { useToast } from '@/components/ui/use-toast' import { useMediaQuery } from '@/lib/hooks' -import { formatCurrency, formatExpenseDate } from '@/lib/utils' +import { formatCurrency, formatExpenseDate, formatFileSize } from '@/lib/utils' import { Category } from '@prisma/client' import { ChevronRight, FileQuestion, Loader2, Receipt } from 'lucide-react' import { getImageData, usePresignedUpload } from 'next-s3-upload' @@ -40,6 +40,8 @@ type Props = { categories: Category[] } +const MAX_FILE_SIZE = 5 * 1024 ** 2 + export function CreateFromReceiptButton({ groupId, groupCurrency, @@ -56,6 +58,17 @@ export function CreateFromReceiptButton({ const isDesktop = useMediaQuery('(min-width: 640px)') const handleFileChange = async (file: File) => { + if (file.size > MAX_FILE_SIZE) { + toast({ + title: 'The file is too big', + description: `The maximum file size you can upload is ${formatFileSize( + MAX_FILE_SIZE, + )}. Yours is ${formatFileSize(file.size)}.`, + variant: 'destructive', + }) + return + } + const upload = async () => { try { setPending(true) diff --git a/src/components/expense-documents-input.tsx b/src/components/expense-documents-input.tsx index 7b69a6d..ae8a272 100644 --- a/src/components/expense-documents-input.tsx +++ b/src/components/expense-documents-input.tsx @@ -17,6 +17,7 @@ import { ToastAction } from '@/components/ui/toast' import { useToast } from '@/components/ui/use-toast' import { randomId } from '@/lib/api' import { ExpenseFormValues } from '@/lib/schemas' +import { formatFileSize } from '@/lib/utils' import { Loader2, Plus, Trash, X } from 'lucide-react' import { getImageData, usePresignedUpload } from 'next-s3-upload' import Image from 'next/image' @@ -27,12 +28,25 @@ type Props = { updateDocuments: (documents: ExpenseFormValues['documents']) => void } +const MAX_FILE_SIZE = 5 * 1024 ** 2 + export function ExpenseDocumentsInput({ documents, updateDocuments }: Props) { const [pending, setPending] = useState(false) const { FileInput, openFileDialog, uploadToS3 } = usePresignedUpload() // use presigned uploads to addtionally support providers other than AWS const { toast } = useToast() const handleFileChange = async (file: File) => { + if (file.size > MAX_FILE_SIZE) { + toast({ + title: 'The file is too big', + description: `The maximum file size you can upload is ${formatFileSize( + MAX_FILE_SIZE, + )}. Yours is ${formatFileSize(file.size)}.`, + variant: 'destructive', + }) + return + } + const upload = async () => { try { setPending(true) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 2ce486c..06d1989 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -29,3 +29,16 @@ export function formatCurrency(currency: string, amount: number) { const formattedAmount = format.format(amount / 100) return `${currency} ${formattedAmount}` } + +export function formatFileSize(size: number) { + const formatNumber = (num: number) => + num.toLocaleString('en-US', { + minimumFractionDigits: 0, + maximumFractionDigits: 1, + }) + + if (size > 1024 ** 3) return `${formatNumber(size / 1024 ** 3)} GB` + if (size > 1024 ** 2) return `${formatNumber(size / 1024 ** 2)} MB` + if (size > 1024) return `${formatNumber(size / 1024)} kB` + return `${formatNumber(size)} B` +}