-
Notifications
You must be signed in to change notification settings - Fork 328
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
chore: add server uploads to pg #1055
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import * as React from "react"; | ||
import cx from "clsx"; | ||
|
||
export function Input({ | ||
className, | ||
type, | ||
...props | ||
}: React.ComponentProps<"input">) { | ||
return ( | ||
<input | ||
type={type} | ||
className={cx( | ||
className, | ||
"border-input focus-visible:border-ring focus-visible:ring-ring/30 flex w-full rounded-lg border px-3 py-2 text-sm shadow-sm shadow-black/5 transition-shadow focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", | ||
type === "search" && | ||
"[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none [&::-webkit-search-results-button]:appearance-none [&::-webkit-search-results-decoration]:appearance-none", | ||
type === "file" && | ||
"file:border-input p-0 pr-3 italic file:me-3 file:h-full file:border-0 file:border-r file:border-solid file:bg-transparent file:px-3 file:text-sm file:font-medium file:not-italic", | ||
)} | ||
{...props} | ||
/> | ||
); | ||
} | ||
|
||
export function Label({ className, ...props }: React.ComponentProps<"label">) { | ||
return ( | ||
<label | ||
className={cx( | ||
"text-sm font-medium leading-4 peer-disabled:cursor-not-allowed peer-disabled:opacity-70", | ||
className, | ||
)} | ||
{...props} | ||
/> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,35 +1,87 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"use client"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { useActionState, useEffect, useRef } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { useRouter } from "next/navigation"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { generateReactHelpers, generateUploadButton } from "@uploadthing/react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { UploadRouter } from "../app/api/uploadthing/route"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { uploadFiles } from "../lib/actions"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { Input, Label } from "./fieldset"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export const UTButton = generateUploadButton<UploadRouter>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export const { useUploadThing } = generateReactHelpers<UploadRouter>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
function ServerUploader(props: { type: "file" | "url" }) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const formRef = useRef<HTMLFormElement>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const [state, dispatch, isUploading] = useActionState(uploadFiles, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
success: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
error: "", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (state.success === false && state.error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
window.alert(state.error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, [state.success]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<form ref={formRef} action={dispatch}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="space-y-1"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Label>Upload (server {props.type})</Label> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Input | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
name="files" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
multiple | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
disabled={isUploading} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
className="h-10 p-0 file:me-3 file:border-0 file:border-e" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type={props.type === "file" ? "file" : "text"} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onChange={() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (props.type === "file") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
formRef.current?.requestSubmit(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+32
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix incomplete URL input handling The Input component has Consider this fix: <Input
name="files"
- multiple
+ multiple={props.type === "file"}
disabled={isUploading}
className="h-10 p-0 file:me-3 file:border-0 file:border-e"
type={props.type === "file" ? "file" : "text"}
onChange={() => {
if (props.type === "file") {
formRef.current?.requestSubmit();
return;
}
+ // For URL type, submit on Enter key press instead
}}
+ onKeyDown={(e) => {
+ if (props.type === "url" && e.key === "Enter") {
+ e.preventDefault();
+ formRef.current?.requestSubmit();
+ }
+ }}
/> 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<noscript> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<button type="submit" disabled={isUploading}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{isUploading ? "⏳" : `Upload (server ${props.type})`} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</noscript> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</form> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export function Uploader() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const router = useRouter(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<UTButton | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
endpoint="anything" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
input={{}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onUploadError={(error) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
window.alert(error.message); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onClientUploadComplete={() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
router.refresh(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
content={{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
allowedContent: <></>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
button: ({ isUploading }) => (isUploading ? null : "Upload"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
appearance={{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
button: "!text-sm/6", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
allowedContent: "!h-0", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="flex gap-4"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="space-y-1"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Label>Upload (client)</Label> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<UTButton | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
endpoint="anything" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
input={{}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onUploadError={(error) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
window.alert(error.message); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onClientUploadComplete={() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
router.refresh(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
content={{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
allowedContent: <></>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
button: ({ isUploading }) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
isUploading ? null : "Upload (Client)", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
appearance={{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
button: "!text-sm/6", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
allowedContent: "!h-0", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<ServerUploader type="file" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<ServerUploader type="url" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -5,6 +5,7 @@ import { cookies } from "next/headers"; | |||||||||||||||||||||||||
import { redirect } from "next/navigation"; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
import { UTApi } from "uploadthing/server"; | ||||||||||||||||||||||||||
import { UploadFileResult } from "uploadthing/types"; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
import { CACHE_TAGS, SESSION_COOKIE_NAME } from "./const"; | ||||||||||||||||||||||||||
import { getSession, Session } from "./data"; | ||||||||||||||||||||||||||
|
@@ -35,6 +36,42 @@ export async function signOut() { | |||||||||||||||||||||||||
redirect("/"); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
export async function uploadFiles(previousState: unknown, form: FormData) { | ||||||||||||||||||||||||||
const session = await getSession(); | ||||||||||||||||||||||||||
if (!session) { | ||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||
success: false as const, | ||||||||||||||||||||||||||
error: "Unauthorized", | ||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
const files = form.getAll("files") as File[] | string[]; | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add type validation for file input The type casting of FormData values to -const files = form.getAll("files") as File[] | string[];
+const files = form.getAll("files").map((file) => {
+ if (!(file instanceof File) && typeof file !== "string") {
+ throw new Error("Invalid file type");
+ }
+ return file;
+}); 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||
let uploadResults: UploadFileResult[]; | ||||||||||||||||||||||||||
if (files.some((file) => typeof file === "string")) { | ||||||||||||||||||||||||||
uploadResults = await utapi.uploadFilesFromUrl(files as string[]); | ||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||
uploadResults = await utapi.uploadFiles(files as File[]); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
if (uploadResults.every((result) => result.error !== null)) { | ||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||
success: false as const, | ||||||||||||||||||||||||||
error: "Failed to upload some files", | ||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
Comment on lines
+56
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix inverted error check logic The current logic checks if ALL results have errors, but it should check if ANY result has an error. -if (uploadResults.every((result) => result.error !== null)) {
+if (uploadResults.some((result) => result.error !== null)) {
return {
success: false as const,
error: "Failed to upload some files",
};
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
revalidateTag(CACHE_TAGS.LIST_FILES); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
const uploadedCount = uploadResults.filter( | ||||||||||||||||||||||||||
(result) => result.data != null, | ||||||||||||||||||||||||||
).length; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||
success: uploadedCount === uploadResults.length, | ||||||||||||||||||||||||||
uploadedCount, | ||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
export async function getFileUrl(key: string) { | ||||||||||||||||||||||||||
const session = await getSession(); | ||||||||||||||||||||||||||
if (!session) { | ||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import * as React from "react"; | ||
import cx from "clsx"; | ||
|
||
export function Input({ | ||
className, | ||
type, | ||
...props | ||
}: React.ComponentProps<"input">) { | ||
return ( | ||
<input | ||
type={type} | ||
className={cx( | ||
className, | ||
"border-input focus-visible:border-ring focus-visible:ring-ring/30 flex w-full rounded-lg border px-3 py-2 text-sm shadow-sm shadow-black/5 transition-shadow focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", | ||
type === "search" && | ||
"[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none [&::-webkit-search-results-button]:appearance-none [&::-webkit-search-results-decoration]:appearance-none", | ||
type === "file" && | ||
"file:border-input p-0 pr-3 italic file:me-3 file:h-full file:border-0 file:border-r file:border-solid file:bg-transparent file:px-3 file:text-sm file:font-medium file:not-italic", | ||
)} | ||
{...props} | ||
/> | ||
); | ||
} | ||
|
||
export function Label({ className, ...props }: React.ComponentProps<"label">) { | ||
return ( | ||
<label | ||
className={cx( | ||
"text-sm font-medium leading-4 peer-disabled:cursor-not-allowed peer-disabled:opacity-70", | ||
className, | ||
)} | ||
{...props} | ||
/> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,87 @@ | ||
"use client"; | ||
|
||
import { useActionState, useEffect, useRef } from "react"; | ||
import { useRouter } from "next/navigation"; | ||
|
||
import { generateReactHelpers, generateUploadButton } from "@uploadthing/react"; | ||
|
||
import { UploadRouter } from "../app/api/uploadthing/route"; | ||
import { uploadFiles } from "../lib/actions"; | ||
import { Input, Label } from "./fieldset"; | ||
|
||
export const UTButton = generateUploadButton<UploadRouter>(); | ||
export const { useUploadThing } = generateReactHelpers<UploadRouter>(); | ||
|
||
function ServerUploader(props: { type: "file" | "url" }) { | ||
const formRef = useRef<HTMLFormElement>(null); | ||
const [state, dispatch, isUploading] = useActionState(uploadFiles, { | ||
success: false, | ||
error: "", | ||
}); | ||
|
||
useEffect(() => { | ||
if (state.success === false && state.error) { | ||
window.alert(state.error); | ||
} | ||
}, [state.success]); | ||
|
||
return ( | ||
<form ref={formRef} action={dispatch}> | ||
<div className="space-y-1"> | ||
<Label>Upload (server {props.type})</Label> | ||
<Input | ||
name="files" | ||
multiple | ||
disabled={isUploading} | ||
className="h-10 p-0 file:me-3 file:border-0 file:border-e" | ||
type={props.type === "file" ? "file" : "text"} | ||
onChange={() => { | ||
if (props.type === "file") { | ||
formRef.current?.requestSubmit(); | ||
return; | ||
} | ||
}} | ||
Comment on lines
+38
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Incomplete onChange handler implementation The onChange handler only handles the file type case but doesn't implement any logic for URL type. This could lead to a confusing user experience when entering URLs. Consider implementing URL validation and auto-submission: onChange={() => {
if (props.type === "file") {
formRef.current?.requestSubmit();
return;
}
+ if (props.type === "url") {
+ const input = formRef.current?.querySelector('input');
+ const url = input?.value;
+ if (url && isValidUrl(url)) {
+ formRef.current?.requestSubmit();
+ }
+ }
}}
|
||
/> | ||
</div> | ||
|
||
<noscript> | ||
<button type="submit" disabled={isUploading}> | ||
{isUploading ? "⏳" : `Upload (server ${props.type})`} | ||
</button> | ||
</noscript> | ||
</form> | ||
); | ||
} | ||
|
||
export function Uploader() { | ||
const router = useRouter(); | ||
|
||
return ( | ||
<UTButton | ||
endpoint={(rr) => rr.anything} | ||
input={{}} | ||
onUploadError={(error) => { | ||
window.alert(error.message); | ||
}} | ||
onClientUploadComplete={() => { | ||
router.refresh(); | ||
}} | ||
content={{ | ||
allowedContent: <></>, | ||
button: ({ isUploading }) => (isUploading ? null : "Upload"), | ||
}} | ||
appearance={{ | ||
button: "!text-sm/6", | ||
allowedContent: "!h-0", | ||
}} | ||
/> | ||
<div className="flex gap-4"> | ||
<div className="space-y-1"> | ||
<Label>Upload (client)</Label> | ||
<UTButton | ||
endpoint={(rr) => rr.anything} | ||
input={{}} | ||
onUploadError={(error) => { | ||
window.alert(error.message); | ||
}} | ||
onClientUploadComplete={() => { | ||
router.refresh(); | ||
}} | ||
content={{ | ||
allowedContent: <></>, | ||
button: ({ isUploading }) => | ||
isUploading ? null : "Upload (Client)", | ||
}} | ||
appearance={{ | ||
button: "!text-sm/6", | ||
allowedContent: "!h-0", | ||
}} | ||
/> | ||
</div> | ||
<ServerUploader type="file" /> | ||
<ServerUploader type="url" /> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -5,6 +5,7 @@ import { cookies } from "next/headers"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { redirect } from "next/navigation"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { UTApi } from "uploadthing/server"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { UploadFileResult } from "uploadthing/types"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { CACHE_TAGS, SESSION_COOKIE_NAME } from "./const"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { getSession, Session } from "./data"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -35,6 +36,42 @@ export async function signOut() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
redirect("/"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export async function uploadFiles(previousState: unknown, form: FormData) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const session = await getSession(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!session) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
success: false as const, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
error: "Unauthorized", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const files = form.getAll("files") as File[] | string[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let uploadResults: UploadFileResult[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (files.some((file) => typeof file === "string")) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
uploadResults = await utapi.uploadFilesFromUrl(files as string[]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
uploadResults = await utapi.uploadFiles(files as File[]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (uploadResults.every((result) => result.error !== null)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
success: false as const, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
error: "Failed to upload some files", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
revalidateTag(CACHE_TAGS.LIST_FILES); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const uploadedCount = uploadResults.filter( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(result) => result.data != null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
).length; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
success: uploadedCount === uploadResults.length, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
uploadedCount, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+56
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Enhance error handling and logging. The current error handling could be improved by:
if (uploadResults.every((result) => result.error !== null)) {
+ const errors = uploadResults
+ .map((result) => result.error)
+ .filter(Boolean);
+ console.error("File upload errors:", errors);
return {
success: false as const,
- error: "Failed to upload some files",
+ error: "Failed to upload files",
+ details: errors,
};
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export async function getFileUrl(key: string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const session = await getSession(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!session) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve error handling UX
Using
window.alert
for error messages isn't ideal for accessibility and user experience.Consider using a toast notification system or an inline error message: