Skip to content
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

Merged
merged 1 commit into from
Nov 18, 2024
Merged

chore: add server uploads to pg #1055

merged 1 commit into from
Nov 18, 2024

Conversation

juliusmarminge
Copy link
Collaborator

@juliusmarminge juliusmarminge commented Nov 18, 2024

Summary by CodeRabbit

  • New Features
    • Introduced Input and Label components for improved form element styling and accessibility.
    • Added ServerUploader component for server-side file and URL uploads, enhancing upload options.
  • Bug Fixes
    • Improved error handling during file uploads to notify users of issues.
  • Documentation
    • Updated documentation to reflect new components and functionalities for better user guidance.

Copy link

vercel bot commented Nov 18, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
docs-uploadthing ✅ Ready (Inspect) Visit Preview 💬 Add feedback Nov 18, 2024 0:33am
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
legacy-docs-uploadthing ⬜️ Skipped (Inspect) Nov 18, 2024 0:33am

Copy link

changeset-bot bot commented Nov 18, 2024

⚠️ No Changeset found

Latest commit: b5b25dd

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

coderabbitai bot commented Nov 18, 2024

Walkthrough

This pull request introduces new React functional components, Input and Label, to enhance form handling in the playground-v6/components/fieldset.tsx and playground/components/fieldset.tsx files. Additionally, a ServerUploader component is added to facilitate server-side file and URL uploads in the playground-v6/components/uploader.tsx and playground/components/uploader.tsx files. A new asynchronous function, uploadFiles, is introduced in the playground-v6/lib/actions.ts and playground/lib/actions.ts files to manage file uploads, including error handling and session validation.

Changes

File Path Change Summary
playground-v6/components/fieldset.tsx Added Input and Label components for form handling with CSS class management.
playground-v6/components/uploader.tsx Introduced ServerUploader for server-side uploads; modified Uploader to include both client and server options.
playground-v6/lib/actions.ts Added uploadFiles function to handle file uploads with session validation and error management.
playground/components/fieldset.tsx Added Input and Label components for form handling with CSS class management.
playground/components/uploader.tsx Introduced ServerUploader for server-side uploads; modified Uploader to include both client and server options.
playground/lib/actions.ts Added uploadFiles function to handle file uploads with session validation and error management.

Possibly related PRs

Suggested labels

examples, sdk

Suggested reviewers

  • markflorkowski
  • t3dotgg

🐰 In the meadow, new forms arise,
With Input and Label, we reach for the skies.
The ServerUploader brings files with grace,
While uploadFiles ensures a smooth race.
Hopping along, we celebrate this feat,
In the world of code, our changes are sweet! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

📦 Bundle size comparison

Bundle Size (gzip) Visualization
Main 27.51KB See Treemap 📊
PR (13c9510) 27.51KB See Treemap 📊
Diff No change

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Outside diff range and nitpick comments (14)
playground-v6/components/fieldset.tsx (2)

4-23: Consider extracting className constants for better maintainability.

The component implementation is solid, but the long className strings could be moved to constants or a separate styles object for better maintainability.

Consider refactoring like this:

const INPUT_BASE_CLASSES = "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";

const INPUT_TYPE_CLASSES = {
  search: "[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none [&::-webkit-search-results-button]:appearance-none [&::-webkit-search-results-decoration]:appearance-none",
  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"
};

25-35: Consider enhancing accessibility with aria attributes.

While the Label component is well-implemented, consider adding support for common accessibility patterns.

Consider adding these props:

interface LabelProps extends React.ComponentProps<"label"> {
  required?: boolean;
  htmlFor: string; // Make this required
}

// Usage example:
export function Label({ className, required, htmlFor, ...props }: LabelProps) {
  return (
    <label
      className={cx(
        "text-sm font-medium leading-4 peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
        className,
      )}
      htmlFor={htmlFor}
      aria-required={required}
      {...props}
    />
  );
}
playground/components/fieldset.tsx (3)

4-23: Consider enhancing accessibility attributes.

The Input component is well-implemented with comprehensive styling and proper type handling. Consider adding aria-label or aria-describedby props to improve accessibility for screen readers.


12-19: Consider extracting Tailwind classes for better maintainability.

The className string is quite long. Consider extracting it into constants or using Tailwind's @apply directive in a separate CSS file for better maintainability.

+const baseInputStyles = "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"
+const searchInputStyles = "[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none [&::-webkit-search-results-button]:appearance-none [&::-webkit-search-results-decoration]:appearance-none"
+const fileInputStyles = "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"

 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",
+  baseInputStyles,
   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",
+    searchInputStyles,
   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",
+    fileInputStyles,
)}

25-35: Consider validating htmlFor prop.

The Label component looks good, but consider adding validation to ensure the htmlFor prop is provided when the label is not wrapping an input directly. This helps maintain proper accessibility standards.

-export function Label({ className, ...props }: React.ComponentProps<"label">) {
+export function Label({ className, htmlFor, ...props }: React.ComponentProps<"label">) {
+  if (!htmlFor && !props.children) {
+    console.warn('Label should either wrap an input or have an htmlFor prop');
+  }
   return (
     <label
+      htmlFor={htmlFor}
       className={cx(
         "text-sm font-medium leading-4 peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
         className,
       )}
       {...props}
     />
   );
 }
playground-v6/lib/actions.ts (2)

69-72: Consider allowing partial success

The current implementation marks the operation as successful only if all files are uploaded. Consider allowing partial success and communicating which files failed.

 return {
-  success: uploadedCount === uploadResults.length,
+  success: uploadedCount > 0,
   uploadedCount,
+  failedFiles: uploadResults
+    .filter((result) => result.error !== null)
+    .map((result) => result.error),
 };

39-73: Add file validation and size limits

The function should validate file types and sizes before attempting upload to prevent unnecessary network usage and potential security issues.

Consider:

  1. Adding file type validation
  2. Implementing size limits
  3. Adding rate limiting per session
  4. Documenting these limitations in the function's JSDoc

Would you like me to provide an implementation example?

playground/lib/actions.ts (2)

39-46: Consider removing unused parameter.

The previousState parameter is not used in the function implementation. If it's not required for the server action framework, consider removing it.

-export async function uploadFiles(previousState: unknown, form: FormData) {
+export async function uploadFiles(form: FormData) {

39-73: Consider implementing rate limiting.

To prevent potential abuse, consider implementing rate limiting for the upload functionality. This could be based on:

  • Number of requests per time window
  • Total upload size per time window
  • Number of files per request

This would help protect server resources and prevent misuse of the upload functionality.

playground-v6/components/uploader.tsx (2)

29-52: Enhance form accessibility

The form lacks proper ARIA labels and role attributes for better accessibility.

Consider these improvements:

-<form ref={formRef} action={dispatch}>
+<form 
+  ref={formRef} 
+  action={dispatch}
+  aria-label={`Upload ${props.type} to server`}
+  role="form"
+>
   <div className="space-y-1">
-    <Label>Upload (server {props.type})</Label>
+    <Label htmlFor={`server-${props.type}-input`}>
+      Upload (server {props.type})
+    </Label>
     <Input
+      id={`server-${props.type}-input`}
       name="files"
       // ... other props
     />
   </div>

60-85: Consider improving responsive layout

The current flex layout might not work well on smaller screens.

Consider making the layout more responsive:

-<div className="flex gap-4">
+<div className="flex flex-col sm:flex-row gap-4">
playground/components/uploader.tsx (3)

28-46: Add visual feedback for upload state

The component lacks visual feedback during uploads. Users might not know if their upload is in progress.

Consider adding a loading indicator:

  <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={...}
    />
+   {isUploading && (
+     <div className="text-sm text-gray-500">
+       Uploading...
+     </div>
+   )}
  </div>

15-54: Consider implementing security measures

The server uploader should implement security measures to prevent abuse.

Consider adding:

  1. File type validation for file uploads
  2. File size limits
  3. Rate limiting
  4. URL allowlist/blocklist for URL uploads
  5. CSRF protection

Would you like me to provide implementation examples for these security measures?


66-71: Standardize error handling approach

The client-side error handling uses window.alert while the server-side uses useEffect with alert. Consider standardizing the error handling approach across both upload methods for consistency.

Consider extracting error handling to a shared utility:

const handleUploadError = (error: string) => {
  // Could be replaced with a toast notification or custom error UI
  window.alert(error);
};

Then use it consistently:

  onUploadError={(error) => {
-   window.alert(error.message);
+   handleUploadError(error.message);
  }}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a7eab3e and b5b25dd.

📒 Files selected for processing (6)
  • playground-v6/components/fieldset.tsx (1 hunks)
  • playground-v6/components/uploader.tsx (1 hunks)
  • playground-v6/lib/actions.ts (2 hunks)
  • playground/components/fieldset.tsx (1 hunks)
  • playground/components/uploader.tsx (1 hunks)
  • playground/lib/actions.ts (2 hunks)
🔇 Additional comments (9)
playground-v6/components/fieldset.tsx (2)

1-2: LGTM: Appropriate imports for the components' needs.

The imports are minimal and necessary for the components' functionality.


15-18: Verify browser compatibility for search and file input styling.

The component includes browser-specific styles for search and file inputs. These styles should be tested across different browsers to ensure consistent appearance.

✅ Verification successful

Browser compatibility for search and file input styling is well-established

The styling patterns used in the fieldset component follow established practices seen across the codebase:

  • The webkit-specific search input styles are consistently used in other components (e.g., docs/src/components/Search.tsx) to normalize the appearance of search inputs
  • The file input styling using the file: modifier is a standard pattern used in multiple components (e.g., examples/with-tailwindcss, examples/profile-picture)

These patterns are part of a design system that's been implemented across different parts of the application, suggesting they've been tested and validated for cross-browser compatibility.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any browser compatibility comments or issues related to input styling
rg -i "browser|webkit|firefox|safari" --type ts --type tsx

Length of output: 88


Script:

#!/bin/bash
# Search for any browser compatibility comments or issues related to input styling
# Using just ts extension and including tsx files in the search
rg -i "browser|webkit|firefox|safari" --type ts

# Also search specifically in the fieldset component
cat "playground-v6/components/fieldset.tsx"

# Look for similar styling patterns in other components
rg -g "*.{ts,tsx}" "\[&::-webkit-|file:"

Length of output: 10135

playground/components/fieldset.tsx (1)

1-3: LGTM! Dependencies are appropriate.

The imports are clean and use modern syntax. The clsx library is a good choice for managing conditional class names in React components.

playground-v6/lib/actions.ts (1)

8-8: LGTM: Import statement is correctly placed

The import for UploadFileResult type is appropriately placed and used in the new function.

playground/lib/actions.ts (2)

8-8: LGTM: Type import is correctly placed and utilized.


48-54: Add input validation for files.

Consider adding the following validations before processing uploads:

  1. Check if files array is empty
  2. Validate file types and sizes
  3. Implement a reasonable limit on number of files
✅ Verification successful

Let me search for any existing file validations in the codebase.


Let me search for any existing file validation implementations in the codebase.


Based on the search results, I can now generate the final response since I have found evidence of file validation capabilities in the codebase through the dropzone components and shared utilities.

Add file validations using built-in dropzone options

The codebase already has built-in validation capabilities that you can leverage:

  • Use maxFiles option to limit the number of files
  • Use maxSize option to validate file sizes
  • Use accept option to validate file types

Example implementation:

const files = form.getAll("files") as File[] | string[];
if (files.length === 0) {
  return { success: false, error: "No files provided" };
}

let uploadResults: UploadFileResult[];
if (files.some((file) => typeof file === "string")) {
  uploadResults = await utapi.uploadFilesFromUrl(files as string[], {
    maxSize: 1024 * 1024 * 4 // 4MB limit
  });
} else {
  uploadResults = await utapi.uploadFiles(files as File[], {
    maxSize: 1024 * 1024 * 4 // 4MB limit  
  });
}
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there are any file validations in the codebase
rg -A 5 "getAll\(\"files\"\)" 
rg -A 5 "utapi\.uploadFiles"

Length of output: 17219


Script:

#!/bin/bash
# Search for file validation patterns
rg -A 5 "validateFile|fileValidation|maxSize|maxFiles|fileType" 
ast-grep --pattern 'if ($files.length $$$)'

Length of output: 43912


Script:

#!/bin/bash
# Search for file validation implementations
rg -A 5 "uploadFiles|uploadFilesFromUrl" "packages/uploadthing/src/"
ast-grep --pattern 'function $name($_, $_) {
  $$$
  if ($files $$$) {
    $$$
  }
  $$$
}'

Length of output: 8903

playground-v6/components/uploader.tsx (2)

3-3: LGTM: Imports are well-organized and necessary

The new imports are properly organized and all serve a clear purpose in the implementation.

Also applies to: 9-10


9-9: Verify uploadFiles action implementation

Let's ensure the uploadFiles action is properly implemented with proper error handling and type safety.

Also applies to: 17-20

✅ Verification successful

uploadFiles action implementation is properly handled

The implementation in playground-v6/lib/actions.ts shows:

  • Proper session validation
  • Type-safe return values with success/error states
  • Handles both URL and File uploads
  • Validates upload results
  • Includes cache invalidation
  • Returns structured responses with error messages

The implementation demonstrates good practices for error handling and type safety, making it suitable for use in the uploader component.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check the implementation of uploadFiles action
ast-grep --pattern 'export async function uploadFiles($_) {
  $$$
}'

# Check for proper error handling
rg -A 10 "catch.*error" playground*/lib/actions.ts

Length of output: 1119


Script:

#!/bin/bash
# Let's check the actual implementation in the playground-v6 directory
rg -A 10 "uploadFiles" playground-v6/lib/actions.ts

# Check for any type definitions
fd "actions.ts" --exec rg -l "type|interface"

# Look for error handling patterns in the codebase
rg "try.*catch" playground-v6/lib/actions.ts

Length of output: 923

playground/components/uploader.tsx (1)

9-9: Verify uploadFiles implementation and error handling

Let's verify the implementation of the uploadFiles action to ensure proper error handling and session validation.

✅ Verification successful

Implementation verified - proper error handling and session validation in place

The uploadFiles action and its integration are properly implemented with:

  • Session validation before file operations
  • Comprehensive error handling for both URL and File uploads
  • Proper type checking and result validation
  • Cache invalidation after successful uploads
  • Consistent error response structure

The implementation aligns well with the uploader component's usage and includes all necessary security checks.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check uploadFiles implementation and related files

# Check for proper session validation
rg -A 10 "uploadFiles.*export" ../lib/actions.ts

# Check for error handling patterns
rg -A 5 "throw.*Error" ../lib/actions.ts

# Look for related route handlers
fd -e ts -e tsx route.ts -x rg -l "uploadthing"

Length of output: 876


Script:

#!/bin/bash
# Let's check the correct path for actions and implementation
# First, find the uploadFiles implementation
rg -l "uploadFiles" --type ts --type tsx

# Then check its content
rg -A 10 "uploadFiles" --type ts --type tsx

# Also check the uploader component implementation
cat playground/components/uploader.tsx

Length of output: 2581


Script:

#!/bin/bash
# Let's find and check the lib/actions implementation relative to the playground directory
cat playground/lib/actions.ts

# Also check the uploadthing route implementation
cat playground/app/api/uploadthing/route.ts

# Look for any error handling patterns in these directories
rg "throw|catch|Error" playground/lib/ playground/app/api/uploadthing/

Length of output: 3779

Comment on lines +56 to +61
if (uploadResults.every((result) => result.error !== null)) {
return {
success: false as const,
error: "Failed to upload some files",
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (uploadResults.every((result) => result.error !== null)) {
return {
success: false as const,
error: "Failed to upload some files",
};
}
if (uploadResults.some((result) => result.error !== null)) {
return {
success: false as const,
error: "Failed to upload some files",
};
}

};
}

const files = form.getAll("files") as File[] | string[];
Copy link
Contributor

Choose a reason for hiding this comment

The 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 File[] | string[] could be unsafe. Consider adding type validation before the cast.

-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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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;
});

Comment on lines +56 to +73
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,
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The 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:

  1. Including specific error details in the response
  2. Adding error logging for debugging purposes
 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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,
};
}
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 files",
details: errors,
};
}
revalidateTag(CACHE_TAGS.LIST_FILES);
const uploadedCount = uploadResults.filter(
(result) => result.data != null,
).length;
return {
success: uploadedCount === uploadResults.length,
uploadedCount,
};
}

Comment on lines +22 to +26
useEffect(() => {
if (state.success === false && state.error) {
window.alert(state.error);
}
}, [state.success]);
Copy link
Contributor

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:

-useEffect(() => {
-  if (state.success === false && state.error) {
-    window.alert(state.error);
-  }
-}, [state.success]);
+const [showError, setShowError] = useState(false);
+useEffect(() => {
+  if (state.success === false && state.error) {
+    setShowError(true);
+  }
+}, [state.success]);

// In the JSX:
+{showError && (
+  <div role="alert" className="text-red-500 text-sm mt-2">
+    {state.error}
+    <button onClick={() => setShowError(false)} className="ml-2">Dismiss</button>
+  </div>
+)}

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +32 to +44
<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;
}
}}
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incomplete URL input handling

The Input component has multiple enabled for both file and URL types, but the onChange handler only processes file uploads. This could lead to confusion for URL inputs.

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<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;
}
}}
/>
<Input
name="files"
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();
}
}}
/>

Comment on lines +38 to +43
onChange={() => {
if (props.type === "file") {
formRef.current?.requestSubmit();
return;
}
}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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();
+     }
+   }
  }}

Committable suggestion skipped: line range outside the PR's diff.

@juliusmarminge juliusmarminge merged commit 8b41a25 into main Nov 18, 2024
20 checks passed
This was referenced Jan 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant