From 09598cba3e18893a2c3b13afc90a8424d136c15b Mon Sep 17 00:00:00 2001 From: Pau Matas <61761147+PauMatas@users.noreply.github.com> Date: Tue, 26 Mar 2024 17:49:32 +0100 Subject: [PATCH] 4 multiples pdfs (#79) * update: uploadthing to latest * checkpoint: form, api/post/create and dropzone ready. BD and PostView have to be adapted. * add: new column in Post with correct values * feat: all scripts adapted to urls Maybe it is time to do files instead of urls * refactor: urls (string[]) to files (File[]) --- .gitignore | 1 - package.json | 9 +- prisma/schema.prisma | 31 ++--- scripts/post-files-from-content.js | 39 ++++++ src/app/api/subject/post/create/route.ts | 11 +- src/app/api/subject/post/vote/route.ts | 3 - src/app/api/uploadthing/core.ts | 27 +--- src/app/api/uploadthing/route.ts | 5 +- src/components/Form.tsx | 23 ++-- src/components/MultiFileDropzone.tsx | 159 +++++++++++++++++++++++ src/components/Post.tsx | 66 +++++++--- src/components/PostFeed.tsx | 2 + src/lib/uploadthing.ts | 7 +- src/lib/utils.ts | 15 +++ src/lib/validators/post.ts | 11 +- src/types/redis.d.ts | 1 - yarn.lock | 84 ++++++++---- 17 files changed, 391 insertions(+), 103 deletions(-) create mode 100644 scripts/post-files-from-content.js create mode 100644 src/components/MultiFileDropzone.tsx diff --git a/.gitignore b/.gitignore index 7f40deb..66b2760 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,3 @@ next-env.d.ts userbase.csv pscale_dump_apunts-dades_main_20240310_112108 -prisma/migrations diff --git a/package.json b/package.json index bbcbd73..d97e2bc 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "lint": "next lint", "postinstall": "prisma generate", "format": "prettier --write .", - "prepare": "husky" + "prepare": "husky", + "post-files-from-content": "node scripts/post-files-from-content.js" }, "prisma": { "seed": "node prisma/seed.js" @@ -60,7 +61,7 @@ "@types/node": "20.2.5", "@types/react": "18.2.7", "@types/react-dom": "18.2.4", - "@uploadthing/react": "^4.1.3", + "@uploadthing/react": "^6.4.0", "@upstash/redis": "^1.21.0", "autoprefixer": "10.4.14", "axios": "^1.4.0", @@ -83,7 +84,7 @@ "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-hook-form": "^7.50.1", - "react-pdf": "^7.7.0", + "react-pdf": "^7.7.1", "react-textarea-autosize": "^8.4.1", "readline-sync": "^1.4.10", "server-only": "^0.0.1", @@ -93,7 +94,7 @@ "tailwindcss-animate": "^1.0.5", "ts-node": "^9.1.1", "typescript": "5.0.4", - "uploadthing": "^4.1.3", + "uploadthing": "^6.6.0", "yarn": "^1.22.21", "zod": "^3.22.4" }, diff --git a/prisma/schema.prisma b/prisma/schema.prisma index bef6404..eb8ba28 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -42,7 +42,7 @@ model User { username String? @unique generacio Int @default(2020) image String? - isAdmin Boolean @default(false) + isAdmin Boolean @default(false) accounts Account[] sessions Session[] posts Post[] @@ -97,21 +97,22 @@ model Question { } model Post { - id String @id @default(cuid()) - title String - content String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - subjectId String - authorId String - tipus TipusType - year Int + id String @id @default(cuid()) + title String + content String? + files Json @default(dbgenerated()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + subjectId String + authorId String + tipus TipusType + year Int + isAnonymous Boolean @default(false) NonUploaderAuthorEmail String? - isAnonymous Boolean @default(false) - subject Subject @relation(fields: [subjectId], references: [id]) - author User @relation(fields: [authorId], references: [id]) - comments Comment[] - votes PostVote[] + subject Subject @relation(fields: [subjectId], references: [id]) + author User @relation(fields: [authorId], references: [id]) + comments Comment[] + votes PostVote[] @@index([title]) } diff --git a/scripts/post-files-from-content.js b/scripts/post-files-from-content.js new file mode 100644 index 0000000..a4bc04c --- /dev/null +++ b/scripts/post-files-from-content.js @@ -0,0 +1,39 @@ +const { PrismaClient } = require("@prisma/client") +const { default: axios } = require("axios") + +const prisma = new PrismaClient() + +async function updateRecords() { + try { + const recordsToUpdate = await prisma.Post.findMany() + + for (const record of recordsToUpdate) { + const response = await axios.get(record.content, { + responseType: "blob", + }) + const fileObject = new File([response.data], "PDF Sense Nom") + + await prisma.Post.update({ + where: { id: record.id }, + data: { + files: JSON.stringify([ + { + name: fileObject.name, + size: fileObject.size, + type: fileObject.type, + url: record.content, + }, + ]), + }, + }) + } + + console.log("Records updated successfully.") + } catch (error) { + console.error("Error updating records:", error) + } finally { + await prisma.$disconnect() + } +} + +updateRecords() diff --git a/src/app/api/subject/post/create/route.ts b/src/app/api/subject/post/create/route.ts index ab5350d..728ade6 100644 --- a/src/app/api/subject/post/create/route.ts +++ b/src/app/api/subject/post/create/route.ts @@ -62,7 +62,16 @@ export async function POST(req: Request) { await db.post.create({ data: { title: title, - content: pdf, + files: JSON.stringify( + pdf.map((file) => { + return { + name: file.name, + size: file.size, + type: file.type, + url: file.url, + } + }), + ), subjectId: subject.id, authorId: authorId, tipus: tipus as TipusType, diff --git a/src/app/api/subject/post/vote/route.ts b/src/app/api/subject/post/vote/route.ts index e600d42..a95bbd8 100644 --- a/src/app/api/subject/post/vote/route.ts +++ b/src/app/api/subject/post/vote/route.ts @@ -63,7 +63,6 @@ export async function PATCH(req: Request) { if (votesAmt >= CACHE_AFTER_UPVOTES) { const cachePayload: CachedPost = { authorName: post.author.name ?? "", - content: post.content ?? "", id: post.id, title: post.title, currentVote: null, @@ -99,7 +98,6 @@ export async function PATCH(req: Request) { if (votesAmt >= CACHE_AFTER_UPVOTES) { const cachePayload: CachedPost = { authorName: post.author.name ?? "", - content: post.content ?? "", id: post.id, title: post.title, currentVote: voteType, @@ -131,7 +129,6 @@ export async function PATCH(req: Request) { if (votesAmt >= CACHE_AFTER_UPVOTES) { const cachePayload: CachedPost = { authorName: post.author.name ?? "", - content: post.content ?? "", id: post.id, title: post.title, currentVote: voteType, diff --git a/src/app/api/uploadthing/core.ts b/src/app/api/uploadthing/core.ts index d317f1e..fc6f42e 100644 --- a/src/app/api/uploadthing/core.ts +++ b/src/app/api/uploadthing/core.ts @@ -1,38 +1,25 @@ import { createUploadthing, type FileRouter } from "uploadthing/next" -import { UploadThingError } from "@uploadthing/shared" +import { UploadThingError } from "uploadthing/server" import { getToken } from "next-auth/jwt" const f = createUploadthing() -// FileRouter for your app, can contain multiple FileRoutes export const ourFileRouter = { - // Define as many FileRoutes as you like, each with a unique routeSlug imageUploader: f({ image: { maxFileSize: "4MB" } }) - // Set permissions and file types for this FileRoute - .middleware(async (req) => { - // This code runs on your server before upload + .middleware(async ({ req }) => { const user = await getToken({ req }) - - // If you throw, the user will not be able to upload - if (!user) - throw new UploadThingError({ - code: "FORBIDDEN", - message: "Unauthorized", - }) - - // Whatever is returned here is accessible in onUploadComplete as `metadata` + if (!user) throw new UploadThingError("Unauthorized") return { userId: user.id } }) .onUploadComplete(async ({}) => {}), - // Another FileRoute (made by myself, not by the library) fileUploader: f({ - pdf: { maxFileCount: 1, maxFileSize: "128MB" }, - text: { maxFileCount: 5 }, + pdf: { maxFileCount: 10, maxFileSize: "32MB" }, + text: { maxFileCount: 10, maxFileSize: "32MB" }, }) - .middleware(async (req) => { + .middleware(async ({ req }) => { const user = await getToken({ req }) - if (!user) throw new UploadThingError({ code: "FORBIDDEN" }) + if (!user) throw new UploadThingError("Unauthorized") return { userId: user.id } }) .onUploadComplete(async ({}) => {}), diff --git a/src/app/api/uploadthing/route.ts b/src/app/api/uploadthing/route.ts index b4310d6..98d11e5 100644 --- a/src/app/api/uploadthing/route.ts +++ b/src/app/api/uploadthing/route.ts @@ -1,8 +1,7 @@ -import { createNextRouteHandler } from "uploadthing/next" +import { createRouteHandler } from "uploadthing/next" import { ourFileRouter } from "./core" -// Export routes for Next App Router -export const { GET, POST } = createNextRouteHandler({ +export const { GET, POST } = createRouteHandler({ router: ourFileRouter, }) diff --git a/src/components/Form.tsx b/src/components/Form.tsx index 5b8cd6b..247dad7 100644 --- a/src/components/Form.tsx +++ b/src/components/Form.tsx @@ -23,9 +23,10 @@ import { Checkbox } from "@/components/ui/checkbox" import { ApuntsPostCreationRequest } from "@/lib/validators/post" import { uploadFiles } from "@/lib/uploadthing" import Fireworks from "react-canvas-confetti/dist/presets/fireworks" +import { MultiFileDropzone } from "@/components/MultiFileDropzone" const formSchema = z.object({ - pdf: z.any(), + pdf: z.array(z.any()), title: z.string({ required_error: "Selecciona un títol", }), @@ -43,6 +44,7 @@ const formSchema = z.object({ required_error: "Selecciona un email", }), }) +type FormSchemaType = z.infer export function ProfileForm({ PreselectedSubject, @@ -123,10 +125,12 @@ export function ProfileForm({ form.setValue("authorEmail", "Uploader") } }, [form, isAdmin]) - async function onSubmit(data: ApuntsPostCreationRequest) { - const [res] = await uploadFiles([data.pdf], "fileUploader") + async function onSubmit(data: FormSchemaType) { + const res = await uploadFiles("fileUploader", { + files: data.pdf, + }) const payload: ApuntsPostCreationRequest = { - pdf: res.fileUrl, + pdf: res, title: data.title, year: Number(data.year), assignatura: data.assignatura, @@ -233,13 +237,10 @@ export function ProfileForm({ Fitxers PDF
- { - if (e.target.files) { - field.onChange(e.target.files[0]) - } + { + field.onChange(acceptedFiles) }} />
diff --git a/src/components/MultiFileDropzone.tsx b/src/components/MultiFileDropzone.tsx new file mode 100644 index 0000000..9e49f92 --- /dev/null +++ b/src/components/MultiFileDropzone.tsx @@ -0,0 +1,159 @@ +"use client" + +import { formatFileSize } from "@/lib/utils" +import { FileIcon, UploadCloudIcon } from "lucide-react" +import * as React from "react" +import { useCallback } from "react" +import { DropzoneOptions, useDropzone } from "react-dropzone" +import { twMerge } from "tailwind-merge" + +const variants = { + base: "relative rounded-md p-4 w-96 flex justify-center items-center flex-col cursor-pointer border border-dashed border-zinc-300 transition-colors duration-200 ease-in-out bg-zinc-100", + active: "border-2", + disabled: + "bg-gray-700 border-white/20 cursor-default pointer-events-none bg-opacity-30", + accept: "border border-blue-500 bg-blue-500 bg-opacity-10", + reject: "border border-red-700 bg-red-700 bg-opacity-10", +} + +type InputProps = { + className?: string + value?: File[] + onChange?: (acceptedFiles: File[]) => void | Promise + onFilesAdded?: (addedFiles: File[]) => void | Promise + disabled?: boolean + dropzoneOptions?: Omit +} + +const ERROR_MESSAGES = { + fileTooLarge(maxSize: number) { + return `The file is too large. Max size is ${formatFileSize(maxSize)}.` + }, + fileInvalidType() { + return "Invalid file type." + }, + tooManyFiles(maxFiles: number) { + return `You can only add ${maxFiles} file(s).` + }, + fileNotSupported() { + return "The file is not supported." + }, +} + +const MultiFileDropzone = React.forwardRef( + ( + { dropzoneOptions, value, className, disabled, onFilesAdded, onChange }, + ref, + ) => { + const onDrop = useCallback((acceptedFiles: File[]) => { + if (acceptedFiles) { + void onFilesAdded?.(acceptedFiles) + void onChange?.(acceptedFiles) + } + }, []) + if (dropzoneOptions?.maxFiles && value?.length) { + disabled = disabled ?? value.length >= dropzoneOptions.maxFiles + } + // dropzone configuration + const { + getRootProps, + getInputProps, + fileRejections, + isFocused, + isDragAccept, + isDragReject, + } = useDropzone({ + disabled, + onDrop, + ...dropzoneOptions, + }) + + // styling + const dropZoneClassName = React.useMemo( + () => + twMerge( + variants.base, + isFocused && variants.active, + disabled && variants.disabled, + (isDragReject ?? fileRejections[0]) && variants.reject, + isDragAccept && variants.accept, + className, + ).trim(), + [ + isFocused, + fileRejections, + isDragAccept, + isDragReject, + disabled, + className, + ], + ) + + // error validation messages + const errorMessage = React.useMemo(() => { + if (fileRejections[0]) { + const { errors } = fileRejections[0] + if (errors[0]?.code === "file-too-large") { + return ERROR_MESSAGES.fileTooLarge(dropzoneOptions?.maxSize ?? 0) + } else if (errors[0]?.code === "file-invalid-type") { + return ERROR_MESSAGES.fileInvalidType() + } else if (errors[0]?.code === "too-many-files") { + return ERROR_MESSAGES.tooManyFiles(dropzoneOptions?.maxFiles ?? 0) + } else { + return ERROR_MESSAGES.fileNotSupported() + } + } + return undefined + }, [fileRejections, dropzoneOptions]) + + return ( +
+
+
+ {/* Main File Input */} +
+ +
+ +
+ drag & drop or click to upload +
+
+
+ + {/* Error Text */} +
{errorMessage}
+
+ + {/* Selected Files */} + {value?.map((file, i) => ( +
+
+ +
+
+ {file.name} +
+
+ {formatFileSize(file.size)} +
+
+
+
+
+ ))} +
+
+ ) + }, +) +MultiFileDropzone.displayName = "MultiFileDropzone" + +export { MultiFileDropzone } diff --git a/src/components/Post.tsx b/src/components/Post.tsx index 15fa30e..ca4c3ac 100644 --- a/src/components/Post.tsx +++ b/src/components/Post.tsx @@ -1,13 +1,13 @@ "use client" -import { formatTimeToNow } from "@/lib/utils" +import { cn, formatFileSize, formatTimeToNow } from "@/lib/utils" import { Post, User, PostVote } from "@prisma/client" -import { MessageSquare } from "lucide-react" +import { FileIcon, MessageSquare, ExternalLink } from "lucide-react" import Link from "next/link" -import { buttonVariants } from "@/components/ui/Button" import { FC, useRef } from "react" import PostVoteClient from "./votes/PostVoteClient" import { Badge } from "@/components/ui/Badge" +import { ClientUploadedFileData } from "uploadthing/types" type PartialVote = Pick @@ -20,6 +20,7 @@ interface PostProps { subjectAcronym: string currentVote?: PartialVote commentAmt: number + partialView?: boolean } const Post: FC = ({ @@ -28,9 +29,16 @@ const Post: FC = ({ currentVote: _currentVote, subjectAcronym, commentAmt, + partialView = false, }) => { const pRef = useRef(null) + const postFiles = Array.isArray(post.files) + ? post.files + : typeof post.files === "string" + ? JSON.parse(post.files) + : [] + return (
@@ -69,22 +77,48 @@ const Post: FC = ({
- {post.content && post.content.endsWith(".pdf") ? ( - - Visualitza els Apunts - - ) : null} - {pRef.current?.clientHeight === 160 ? ( + {/* TODO: If we add text content for posts it needs to be here */} + {postFiles + ? postFiles.map( + (url: ClientUploadedFileData, i: number) => ( +
+ + +
+
+ {url.name} +
+
+ {formatFileSize(url.size)} +
+
+
+ +
+
+ +
+ ), + ) + : null} + {partialView && pRef.current?.clientHeight === 160 ? ( // blur bottom if content is too long -
+ ) : null}
diff --git a/src/components/PostFeed.tsx b/src/components/PostFeed.tsx index 3dcf25c..c021ec7 100644 --- a/src/components/PostFeed.tsx +++ b/src/components/PostFeed.tsx @@ -73,6 +73,7 @@ const PostFeed: FC = ({ initialPosts, subjectAcronym }) => { subjectAcronym={post.subject.acronym} votesAmt={votesAmt} currentVote={currentVote} + partialView={true} /> ) @@ -85,6 +86,7 @@ const PostFeed: FC = ({ initialPosts, subjectAcronym }) => { subjectAcronym={post.subject.acronym} votesAmt={votesAmt} currentVote={currentVote} + partialView={true} /> ) } diff --git a/src/lib/uploadthing.ts b/src/lib/uploadthing.ts index e2a1d98..63e20b8 100644 --- a/src/lib/uploadthing.ts +++ b/src/lib/uploadthing.ts @@ -1,5 +1,6 @@ -import { generateReactHelpers } from "@uploadthing/react/hooks" +import { generateReactHelpers } from "@uploadthing/react" -import type { OurFileRouter } from "@/app//api/uploadthing/core" +import type { OurFileRouter } from "@/app/api/uploadthing/core" -export const { uploadFiles } = generateReactHelpers() +export const { useUploadThing, uploadFiles } = + generateReactHelpers() diff --git a/src/lib/utils.ts b/src/lib/utils.ts index a199a3f..2f0374c 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -54,3 +54,18 @@ export function formatTimeToNow(date: Date): string { }, }) } + +export function formatFileSize(bytes?: number) { + if (!bytes) { + return "0 Bytes" + } + bytes = Number(bytes) + if (bytes === 0) { + return "0 Bytes" + } + const k = 1024 + const dm = 2 + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}` +} diff --git a/src/lib/validators/post.ts b/src/lib/validators/post.ts index 69c5c9f..186a522 100644 --- a/src/lib/validators/post.ts +++ b/src/lib/validators/post.ts @@ -6,7 +6,7 @@ export const PostValidator = z.object({ .min(3, { message: "Title must be at least 3 characters long" }) .max(128, { message: "Title must be at most 128 characters long" }), subjectId: z.string(), - content: z.any(), + files: z.array(z.string()), tipus: z.string(), year: z.number(), }) @@ -17,7 +17,14 @@ export const CommentValidator = z.object({ }) export const ApuntsPostValidator = z.object({ - pdf: z.any(), + pdf: z.array( + z.object({ + name: z.string(), + url: z.string(), + size: z.number(), + type: z.string(), + }), + ), title: z.string(), year: z.number(), assignatura: z.string().min(2).max(6), diff --git a/src/types/redis.d.ts b/src/types/redis.d.ts index 0afe0f4..ed97da8 100644 --- a/src/types/redis.d.ts +++ b/src/types/redis.d.ts @@ -4,7 +4,6 @@ export type CachedPost = { id: string title: string authorName: string - content: string currentVote: PostVote["type"] | null createdAt: Date } diff --git a/yarn.lock b/yarn.lock index 368e1b6..8bec2ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,6 +19,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.24.0": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.1.tgz#431f9a794d173b53720e69a6464abc6f0e2a5c57" + integrity sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ== + dependencies: + regenerator-runtime "^0.14.0" + "@codexteam/icons@^0.0.4": version "0.0.4" resolved "https://registry.npmjs.org/@codexteam/icons/-/icons-0.0.4.tgz" @@ -1218,22 +1225,34 @@ resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@uploadthing/mime-types@^0.2.0": - version "0.2.2" - resolved "https://registry.npmjs.org/@uploadthing/mime-types/-/mime-types-0.2.2.tgz" - integrity sha512-ZUo1JHOPPMZDsUw1mOhhVDIvJGlsjj6T0xJ/YJtulyJwL43S9B5pxg1cHcRuTEgjaxj7B55jiqQ6r9mDrrjH9A== +"@uploadthing/dropzone@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@uploadthing/dropzone/-/dropzone-0.2.1.tgz#e373496b3b59b3cf61c8993b5c12db294c48467e" + integrity sha512-OK4rSFnQ2woJ07t78hTfxjMxXedLoj+Jp34kIEtYPYBTPOnNwoKhDXfRojSu8cBilkQROzIe67i0p6F4B6LQhQ== + dependencies: + file-selector "^0.6.0" -"@uploadthing/react@^4.1.3": - version "4.1.3" - resolved "https://registry.npmjs.org/@uploadthing/react/-/react-4.1.3.tgz" - integrity sha512-PG463gGyFnvaBhG5tf7rLDUQb9WDbHPzcV/1EGvrkli4LoMHtBXmTeD9WujwopJ4LjyVuXLh4hgYm7ZJlMlThQ== +"@uploadthing/mime-types@0.2.6": + version "0.2.6" + resolved "https://registry.yarnpkg.com/@uploadthing/mime-types/-/mime-types-0.2.6.tgz#fff8647dcd161c7673bfd50540c9e4c68750de69" + integrity sha512-mB7XAKy5ARltUWZxb2oE0OwUW5Wplxi7Z3cqCvf/ZFvQ1E6lcGVNNKbmJI8c4GaONyEYnalo0Yl228zzHzyZnQ== + +"@uploadthing/react@^6.4.0": + version "6.4.0" + resolved "https://registry.yarnpkg.com/@uploadthing/react/-/react-6.4.0.tgz#38ffba69697b886318a14d0632376dd91b0c3dac" + integrity sha512-XvlrlgH6LM2Zmw/yzryyI6+6mdANFg9nCYvE4glF81cvqVl6X62nrwC3EhvC/H19ZaW8atwsvtyjNxZS3+l+gA== dependencies: - "@uploadthing/shared" "^5.0.0" + "@uploadthing/dropzone" "0.2.1" + "@uploadthing/shared" "6.3.4" + file-selector "^0.6.0" + tailwind-merge "^2.2.1" -"@uploadthing/shared@^5.0.0": - version "5.2.7" - resolved "https://registry.npmjs.org/@uploadthing/shared/-/shared-5.2.7.tgz" - integrity sha512-gc+i66m0vPQpy5PC783iq1koE8Fm9lFTd3uPrJAr7kXwpNRteJs4dnu6etDkY8EilTjMFA6w4js/AhW8AUDuQg== +"@uploadthing/shared@6.3.4": + version "6.3.4" + resolved "https://registry.yarnpkg.com/@uploadthing/shared/-/shared-6.3.4.tgz#e34cd94e59e04271943443f10eba907760f635fb" + integrity sha512-qMf7BiwQth14Gxt2Zerkrbpy/gIHUIkxBz7aN+OEh1T+z2gv8qLRhMXoWFaU56Q+3/NegdR6J+9jwPWc9WwMug== + dependencies: + std-env "^3.7.0" "@upstash/redis@^1.21.0": version "1.28.4" @@ -1822,6 +1841,11 @@ concat-map@0.0.1: resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +consola@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/consola/-/consola-3.2.3.tgz#0741857aa88cfa0d6fd53f1cff0375136e98502f" + integrity sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ== + console-control-strings@^1.0.0, console-control-strings@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" @@ -4228,10 +4252,10 @@ react-is@^16.13.1: resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-pdf@^7.7.0: - version "7.7.0" - resolved "https://registry.npmjs.org/react-pdf/-/react-pdf-7.7.0.tgz" - integrity sha512-704ObLnRDm5lixL4e6NXNLaincBHGNLo+NGdbO3rEXE963NlNzwLxFpmKcbdXHAMQL4rYJQWb1L0w5IL6y8Osw== +react-pdf@^7.7.1: + version "7.7.1" + resolved "https://registry.yarnpkg.com/react-pdf/-/react-pdf-7.7.1.tgz#8f5c4716a8ca65a0889825ef01e3a37956291334" + integrity sha512-cbbf/PuRtGcPPw+HLhMI1f6NSka8OJgg+j/yPWTe95Owf0fK6gmVY7OXpTxMeh92O3T3K3EzfE0ML0eXPGwR5g== dependencies: clsx "^2.0.0" dequal "^2.0.3" @@ -4646,6 +4670,11 @@ source-map@^0.6.0: resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +std-env@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" + integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== + streamsearch@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz" @@ -4843,6 +4872,13 @@ tailwind-merge@^1.12.0: resolved "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.13.0.tgz" integrity sha512-mUTmDbcU+IhOvJ0c42eLQ/nRkvolTqfpVaVQRSxfJAv9TabS6Y2zW/1wKpKLdKzyL3Gh8j6NTLl6MWNmvOM6kA== +tailwind-merge@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.2.2.tgz#87341e7604f0e20499939e152cd2841f41f7a3df" + integrity sha512-tWANXsnmJzgw6mQ07nE3aCDkCK4QdT3ThPMCzawoYA2Pws7vSTCvz3Vrjg61jVUGfFZPJzxEP+NimbcW+EdaDw== + dependencies: + "@babel/runtime" "^7.24.0" + tailwindcss-animate@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.5.tgz" @@ -5123,13 +5159,15 @@ update-browserslist-db@^1.0.11: escalade "^3.1.1" picocolors "^1.0.0" -uploadthing@^4.1.3: - version "4.1.3" - resolved "https://registry.npmjs.org/uploadthing/-/uploadthing-4.1.3.tgz" - integrity sha512-4cvlRhtld+GwrEE3AsjBLJaqYjZOAuyzwD/69wHcYlT175Ukgw8Yn5xPRAChPIEUmCM7JLBJu6OOneSGP/mRxg== +uploadthing@^6.6.0: + version "6.6.0" + resolved "https://registry.yarnpkg.com/uploadthing/-/uploadthing-6.6.0.tgz#d6e901ec8f0ef0169df02d2a420099b29e45c3d2" + integrity sha512-PccLhKjxK1dBKcY9a5vUG+P+bu35cyB7dtDEm7xUVq/C+nc6hC/rh+vC+9jhoJhga+h+O7Rd7M8ZyBaf6W72sA== dependencies: - "@uploadthing/mime-types" "^0.2.0" - "@uploadthing/shared" "^5.0.0" + "@uploadthing/mime-types" "0.2.6" + "@uploadthing/shared" "6.3.4" + consola "^3.2.3" + std-env "^3.7.0" uri-js@^4.2.2: version "4.4.1"