From 01e3b7ddd3ddbd451f4254a23ec826a89036b26e Mon Sep 17 00:00:00 2001 From: oiov Date: Sat, 12 Jul 2025 11:32:16 +0800 Subject: [PATCH] Add clickable tooltip --- app/api/storage/r2/upload/route.ts | 18 +++ components/file/file-list.tsx | 118 +++++++++--------- components/file/upload.tsx | 19 ++- components/ui/tooltip.tsx | 37 ++++++ locales/en.json | 4 +- locales/zh.json | 4 +- .../migrations/20250702103024/migration.sql | 2 +- public/sw.js.map | 2 +- 8 files changed, 139 insertions(+), 65 deletions(-) diff --git a/app/api/storage/r2/upload/route.ts b/app/api/storage/r2/upload/route.ts index 15c0a4a..d06faba 100644 --- a/app/api/storage/r2/upload/route.ts +++ b/app/api/storage/r2/upload/route.ts @@ -2,10 +2,12 @@ import { NextRequest, NextResponse } from "next/server"; import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; +import { getPlanQuota } from "@/lib/dto/plan"; import { getMultipleConfigs } from "@/lib/dto/system-config"; import { checkUserStatus } from "@/lib/dto/user"; import { createS3Client } from "@/lib/r2"; import { getCurrentUser } from "@/lib/session"; +import { restrictByTimeRange } from "@/lib/team"; import { generateFileKey } from "@/lib/utils"; export async function POST(request: NextRequest) { @@ -42,6 +44,22 @@ export async function POST(request: NextRequest) { }); } + const plan = await getPlanQuota(user.team!); + for (const file of files) { + if (Number(file.size) > Number(plan.stMaxFileSize)) { + return Response.json(`File (${file.name}) size limit exceeded`, { + status: 403, + }); + } + } + const limit = await restrictByTimeRange({ + model: "userFile", + userId: user.id, + limit: Number(plan.stMaxFileCount), + rangeType: "month", + }); + if (limit) return Response.json(limit.statusText, { status: limit.status }); + const R2 = createS3Client( configs.s3_config_01.endpoint, configs.s3_config_01.access_key_id, diff --git a/components/file/file-list.tsx b/components/file/file-list.tsx index 102a46f..29e93b8 100644 --- a/components/file/file-list.tsx +++ b/components/file/file-list.tsx @@ -6,10 +6,12 @@ import { User } from "@prisma/client"; import { Archive, Download, + FileAudio, FileCode, FileSpreadsheet, FileText, FileType2, + FileVideo, Folder, ImageOff, Trash2, @@ -25,20 +27,13 @@ import { formatFileSize, truncateMiddle, } from "@/lib/utils"; -import { useMediaQuery } from "@/hooks/use-media-query"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; +import { ClickableTooltip } from "@/components/ui/tooltip"; import { BucketInfo, DisplayType, FileListData } from "@/components/file"; import { UrlForm } from "../forms/url-form"; import { CopyButton } from "../shared/copy-button"; import { EmptyPlaceholder } from "../shared/empty-placeholder"; import { Icons } from "../shared/icons"; -import { PaginationWrapper } from "../shared/pagination"; import QRCodeEditor from "../shared/qr"; import { TimeAgoIntl } from "../shared/time-ago"; import { Badge } from "../ui/badge"; @@ -52,6 +47,7 @@ import { DropdownMenuTrigger, } from "../ui/dropdown-menu"; import { Modal } from "../ui/modal"; +import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { Skeleton } from "../ui/skeleton"; import { Switch } from "../ui/switch"; import { TableCell, TableRow } from "../ui/table"; @@ -92,7 +88,7 @@ export default function UserFileList({ const [currentSelectFile, setCurrentSelectFile] = useState(); - const isAdmin = action.includes("/admin"); + // const isAdmin = action.includes("/admin"); const getFileUrl = (key: string) => { return `${bucketInfo.custom_domain}/${key}`; @@ -155,7 +151,7 @@ export default function UserFileList({ const handleGenerateShortLink = async (urlId: string) => { if (!shortTarget) return; try { - const response = await fetch(`${action}/r2/short`, { + const response = await fetch(`${action}/r2/files/short`, { method: "PUT", body: JSON.stringify({ urlId, fileId: shortTarget?.id }), }); @@ -208,7 +204,14 @@ export default function UserFileList({ const renderFileLinks = (file: UserFileData, index: number) => ( <> - {!isAdmin && file.shortUrlId && ( +
+ +

+ {file.path} +

+ +
+ {file.shortUrlId && (
)}
- - - - {truncateMiddle(file.path)} - {file.status === 1 && ( - - )} - - + {file.mimeType.startsWith("image/") && file.status === 1 && ( )} {renderFileLinks(file, index)} - - - +
+ } + > +
+ {truncateMiddle(file.path)} + {file.status === 1 && ( + + )} +
+
@@ -352,17 +353,18 @@ export default function UserFileList({ {formatFileSize(file.size || 0)}
- - - - {file.user.name ?? file.user.email} - - +

{file.user.name}

{file.user.email}

-
-
-
+ + } + > +
+ {file.user.name ?? file.user.email} +
+
@@ -492,19 +494,13 @@ export default function UserFileList({ )} {React.cloneElement(getFileIcon(file, bucketInfo), { size: 40 })}
- - - - {truncateMiddle(file.path || "")} - - + {file.mimeType.startsWith("image/") && file.status === 1 && ( )}
- - - +
+ } + > +
+ {truncateMiddle(file.path || "")} +
+ @@ -768,12 +768,12 @@ const getFileIcon = (file: UserFileData, bucketInfo: BucketInfo) => { // 音频文件 if (mimeType.startsWith("audio/")) { - return ; + return ; } // 视频文件 if (mimeType.startsWith("video/")) { - return ; + return ; } // 默认文件图标 diff --git a/components/file/upload.tsx b/components/file/upload.tsx index 6938651..97a87dc 100644 --- a/components/file/upload.tsx +++ b/components/file/upload.tsx @@ -12,6 +12,7 @@ import { X, } from "lucide-react"; import { useTranslations } from "next-intl"; +import { toast } from "sonner"; import { cn, formatFileSize } from "@/lib/utils"; import { useFileUpload } from "@/hooks/use-file-upload"; @@ -62,7 +63,20 @@ export const FileUploader = ({ useEffect(() => { if (selectedFile) { - addFiles(selectedFile); + const outOfLimitSizeFiles = selectedFile.some( + (file) => file.size > Number(plan?.stMaxFileSize ?? 0), + ); + const notOutOfLimitSizeFiles = selectedFile.filter( + (file) => file.size <= Number(plan?.stMaxFileSize ?? 0), + ); + addFiles(notOutOfLimitSizeFiles); + if (outOfLimitSizeFiles) { + toast.warning( + `File size exceeds the limit of ${formatFileSize( + Number(plan?.stMaxFileSize ?? 0), + )}`, + ); + } } }, [selectedFile]); @@ -99,6 +113,7 @@ export const FileUploader = ({ + {t("Limit")}:{" "} {formatFileSize(Number(plan?.stMaxFileSize || "0"), { precision: 0, })}{" "} @@ -327,7 +342,7 @@ export const FileUploader = ({ className="flex items-center gap-2 rounded-md bg-green-500 px-4 py-2 text-white transition-colors hover:bg-green-600 disabled:cursor-not-allowed disabled:bg-gray-400" > - Start Upload + {t("Start Upload")}