Skip to content

Conversation

@stnguyen90
Copy link
Contributor

@stnguyen90 stnguyen90 commented Nov 21, 2025

What does this PR do?

Allow users to upload a file when submitting a support ticket

Test Plan

Tested manually:

image image

Related PRs and Issues

None

Have you read the Contributing Guidelines on issues?

Yes

Summary by CodeRabbit

  • New Features

    • Added file upload to the support wizard (max 5 MB), with attach/remove controls and visible upload list.
    • Support requests now include attachments when submitted and show success or error notifications; form resets and wizard closes on success.
  • Bug Fixes / UX

    • Improved validation: invalid files trigger clear user-facing error messages.

✏️ Tip: You can customize this high-level summary in your review settings.

@appwrite
Copy link

appwrite bot commented Nov 21, 2025

Console (appwrite/console)

Project ID: 688b7bf400350cbd60e9

Sites (1)
Site Status Logs Preview QR
 console-stage
688b7cf6003b1842c9dc
Ready Ready View Logs Preview URL QR Code

Tip

Sites auto-generate unique domains with the pattern https://randomstring.appwrite.network

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 21, 2025

Walkthrough

Adds file-upload support to the support wizard: imports Upload and removeFile, introduces a files state (FileList | null), adds an Upload.Dropzone (maxSize 5 MB) with handleInvalid error handling and an Upload.List for displaying/removing files, and switches submission from JSON to multipart FormData (email, subject, firstName, message, tags[], customFields; conditionally attach the first file). Removes explicit Content-Type header. Emits success/error notifications, resets form data and hides the wizard on success, fetches project options on mount, and performs file/support-data cleanup on destroy.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Verify FormData construction and field names (email, subject, firstName, message, tags[], customFields) align with backend expectations and that only the first file is intentionally attached
  • Confirm removal of explicit Content-Type header is correct and FormData boundary is handled automatically
  • Validate file size enforcement (5 MB) and handleInvalid notification behavior for edge cases (corrupted files, multiple invalid files)
  • Review Upload.List rendering and removeFile helper to ensure files state updates without leaks or stale references
  • Check lifecycle hooks: onMount fetch for project options and onDestroy cleanup/reset of support data and file state

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change—adding attachment/file upload support to the support wizard form.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-support-attachment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@stnguyen90 stnguyen90 marked this pull request as draft November 21, 2025 02:25
@stnguyen90
Copy link
Contributor Author

Pending growth server release.

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

🧹 Nitpick comments (2)
src/routes/(console)/supportWizard.svelte (2)

3-12: Attachment imports and files state look good; consider cleaning up legacy file field.

The new Upload/removeFile imports and files = $state<FileList | null>(null) wiring are consistent and straightforward. One small clean‑up opportunity: $supportData is still being reset with a file property in both onDestroy and resetData, but the actual attachment state is now tracked via the local files variable and sent through FormData. If file on $supportData is no longer used anywhere, consider removing it from the store shape and the resets to avoid confusion and future drift between attachment implementations.

Also applies to: 37-40


115-138: FormData construction is correct; tighten null/undefined handling and clarify attachment semantics.

The switch to FormData and the way you append scalar fields and customFields JSON looks compatible with standard multipart/form-data handling, and the conditional attachment append based on files[0] is sound.

Two refinements worth considering:

  • For values that can be null/undefined at the type level (e.g., $supportData.subject, $supportData.message, possibly $user.email), append with a safe fallback to avoid both TS complaints and accidental "null"/"undefined" strings if validation ever changes. For example:
-        formData.append('email', $user.email);
-        formData.append('subject', $supportData.subject);
-        formData.append('message', $supportData.message);
+        formData.append('email', $user.email ?? '');
+        formData.append('subject', $supportData.subject ?? '');
+        formData.append('message', $supportData.message ?? '');
  • If Upload.Dropzone allows multiple files, users might expect all dropped files to be sent. Either explicitly constrain it to a single file (if supported by the component) or document/ensure that only the first file (files[0]) will ever be uploaded so behavior matches expectations.

These are mostly safety/clarity improvements; the current implementation should work given the existing required fields and user store.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 345acee and ff42fcf.

📒 Files selected for processing (1)
  • src/routes/(console)/supportWizard.svelte (5 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-08T13:20:47.308Z
Learnt from: ItzNotABug
Repo: appwrite/console PR: 2316
File: src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte:29-29
Timestamp: 2025-09-08T13:20:47.308Z
Learning: The Form.svelte component in the Appwrite console creates a FormContext with isSubmitting as writable(false) and expects consumers to work with Svelte writable stores, not plain booleans.

Applied to files:

  • src/routes/(console)/supportWizard.svelte
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: e2e
  • GitHub Check: build
🔇 Additional comments (1)
src/routes/(console)/supportWizard.svelte (1)

172-177: Address empty list rendering and verify Upload.List item interface with the component's documentation.

The list visibility issue is confirmed: an empty FileList evaluates to truthy, so {#if files} at line 296 would render an empty Upload.List. Update to {#if files && files.length > 0} to prevent this.

For the item shape at lines 298–305, the current code passes extension: f.type (a MIME type like "application/pdf"), but the property name suggests a file extension string. The pattern is consistent across the codebase, but the @appwrite.io/pink-svelte component definition could not be accessed from the git-based package reference. Please verify the expected item interface for Upload.List (whether it expects MIME type vs. file extension, and whether any additional keys like id or type are needed) to ensure correctness.

@stnguyen90 stnguyen90 self-assigned this Nov 21, 2025
@stnguyen90 stnguyen90 force-pushed the feat-support-attachment branch from ff42fcf to f5f5883 Compare December 20, 2025 00:33
@stnguyen90 stnguyen90 marked this pull request as ready for review December 20, 2025 00:44
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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/routes/(console)/supportWizard.svelte (1)

169-170: Reset files state after successful submission.

The resetData() function resets $supportData but doesn't clear the files state. After a successful submission, any selected files will remain visible in the UI when the wizard is reopened, creating a confusing user experience.

🔎 Suggested fix to reset files state
     }
     resetData();
+    files = null;
     wizard.hide();
 }
🧹 Nitpick comments (3)
src/routes/(console)/supportWizard.svelte (3)

187-192: Consider a more descriptive error message.

The generic "Invalid file" message doesn't inform users why their file was rejected (e.g., exceeds 5MB limit, unsupported type). A more specific message would improve the user experience.

🔎 Example of more descriptive error handling
-function handleInvalid(_e: CustomEvent) {
+function handleInvalid(e: CustomEvent) {
+    const reason = e.detail?.reason || 'file exceeds the 5MB size limit';
     addNotification({
         type: 'error',
-        message: 'Invalid file'
+        message: `Invalid file: ${reason}`
     });
 }

Note: Verify the structure of the detail object from the Upload.Dropzone component to ensure correct property access.


304-310: Consider adding file type restrictions for better UX.

The Upload.Dropzone currently only enforces a 5MB size limit but doesn't restrict file types. Adding frontend file type validation would provide immediate feedback to users and reduce invalid submissions.

🔎 Example with file type restrictions
-<Upload.Dropzone bind:files on:invalid={handleInvalid} maxSize={5 * 1024 * 1024}>
+<Upload.Dropzone 
+    bind:files 
+    on:invalid={handleInvalid} 
+    maxSize={5 * 1024 * 1024}
+    accept="image/*,.pdf,.doc,.docx,.txt,.log">
     <Layout.Stack alignItems="center" gap="s">
         <Typography.Text variant="l-500"
             >Drag and drop a file here or click to upload</Typography.Text>
         <Typography.Caption variant="400">Max file size: 5MB</Typography.Caption>

Adjust the accept attribute based on what file types are appropriate for support tickets. This is optional since the backend should perform definitive validation.


103-112: Consider resetting files state in onDestroy.

The onDestroy hook resets $supportData but doesn't clear the files state. For consistency and to prevent any potential stale state, consider also resetting files = null here.

🔎 Suggested addition
 onDestroy(() => {
     $supportData = {
         message: null,
         subject: null,
         category: 'technical',
         topic: undefined,
         severity: 'question',
         file: null
     };
+    files = null;
 });
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff42fcf and f5f5883.

📒 Files selected for processing (1)
  • src/routes/(console)/supportWizard.svelte (5 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx,js,jsx,svelte}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx,svelte}: Import reusable modules from the src/lib directory using the $lib alias
Use minimal comments in code; reserve comments for TODOs or complex logic explanations
Use $lib, $routes, and $themes aliases instead of relative paths for module imports

Files:

  • src/routes/(console)/supportWizard.svelte
src/routes/**/*.svelte

📄 CodeRabbit inference engine (AGENTS.md)

Use SvelteKit file conventions: +page.svelte for components, +page.ts for data loaders, +layout.svelte for wrappers, +error.svelte for error handling, and dynamic route params in square brackets like [param]

Files:

  • src/routes/(console)/supportWizard.svelte
**/*.{ts,tsx,js,jsx,svelte,json}

📄 CodeRabbit inference engine (AGENTS.md)

Use 4 spaces for indentation, single quotes, 100 character line width, and no trailing commas per Prettier configuration

Files:

  • src/routes/(console)/supportWizard.svelte
**/*.svelte

📄 CodeRabbit inference engine (AGENTS.md)

Use Svelte 5 + SvelteKit 2 syntax with TypeScript for component development

Files:

  • src/routes/(console)/supportWizard.svelte
src/routes/**

📄 CodeRabbit inference engine (AGENTS.md)

Configure dynamic routes using SvelteKit convention with [param] syntax in route directory names

Files:

  • src/routes/(console)/supportWizard.svelte
🧠 Learnings (2)
📚 Learning: 2025-09-08T13:20:47.308Z
Learnt from: ItzNotABug
Repo: appwrite/console PR: 2316
File: src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte:29-29
Timestamp: 2025-09-08T13:20:47.308Z
Learning: The Form.svelte component in the Appwrite console creates a FormContext with isSubmitting as writable(false) and expects consumers to work with Svelte writable stores, not plain booleans.

Applied to files:

  • src/routes/(console)/supportWizard.svelte
📚 Learning: 2025-11-25T03:15:27.539Z
Learnt from: CR
Repo: appwrite/console PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T03:15:27.539Z
Learning: Applies to **/*.svelte : Use Svelte 5 + SvelteKit 2 syntax with TypeScript for component development

Applied to files:

  • src/routes/(console)/supportWizard.svelte
🔇 Additional comments (2)
src/routes/(console)/supportWizard.svelte (2)

3-12: LGTM!

The new imports for Upload component and removeFile helper are properly structured using the $lib alias as per coding guidelines.

Also applies to: 37-37


40-40: LGTM!

Proper use of Svelte 5 $state syntax with appropriate FileList | null typing.

Comment on lines +146 to +148
if (files && files.length > 0) {
formData.append('attachment', files[0]);
}
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all Svelte files to understand structure
find . -type f -name "*.svelte" | grep -i "upload\|dropzone" | head -20

# Search for Upload.Dropzone usage in the codebase
rg -l "Upload\.Dropzone" --type svelte

# Find where Upload is imported from
rg "import.*Upload" --type svelte | head -10

Repository: appwrite/console

Length of output: 254


🏁 Script executed:

#!/bin/bash
# Search for Upload.Dropzone without file type restrictions
rg "Upload\.Dropzone" | head -20

# Also search for supportWizard.svelte file
find . -name "supportWizard.svelte" -o -name "*supportWizard*"

# Search for Upload imports
rg "from.*Upload|import.*Upload" | grep -v node_modules | head -15

Repository: appwrite/console

Length of output: 4512


🏁 Script executed:

#!/bin/bash
# Read the supportWizard.svelte file to see context around lines 146-148
wc -l ./src/routes/\(console\)/supportWizard.svelte

# Read the full file to understand the context
cat -n ./src/routes/\(console\)/supportWizard.svelte | head -200

Repository: appwrite/console

Length of output: 7796


🏁 Script executed:

#!/bin/bash
# Continue reading the file to see the Upload.Dropzone component
cat -n ./src/routes/\(console\)/supportWizard.svelte | sed -n '200,300p'

Repository: appwrite/console

Length of output: 5134


🏁 Script executed:

#!/bin/bash
# Continue reading to find the Upload.Dropzone component
cat -n ./src/routes/\(console\)/supportWizard.svelte | sed -n '300,364p'

Repository: appwrite/console

Length of output: 3588


🏁 Script executed:

#!/bin/bash
# Search for Upload.Dropzone component definition in pink-svelte
find . -path ./node_modules -prune -o -type f -name "*.ts" -o -name "*.js" -o -name "*.svelte" | xargs grep -l "export.*Dropzone" 2>/dev/null | grep -i upload | head -10

# Also check if there's any documentation or type definitions
find . -path ./node_modules -prune -o -type f \( -name "*.d.ts" -o -name "README.md" \) | xargs grep -l "Dropzone\|multiple" 2>/dev/null | head -10

Repository: appwrite/console

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Search for other usages of Upload.Dropzone in the codebase to see if multiple prop is ever used
rg "Upload\.Dropzone" -A 2 -B 2 | head -80

# Also check if there's a node_modules directory to check the library
ls -la node_modules/@appwrite.io/pink-svelte 2>/dev/null | head -20

Repository: appwrite/console

Length of output: 8028


Only the first file is sent; restrict to single-file or send all files.

The code appends only files[0] to FormData, but the Upload.Dropzone component allows multiple selections and the Upload.List component displays all selected files with individual remove options. This creates a confusing UX where users can select and manage multiple files in the UI but only the first one is actually submitted. Either restrict the dropzone to single-file mode or modify the submission to send all selected files.

🤖 Prompt for AI Agents
In src/routes/(console)/supportWizard.svelte around lines 146-148, the form
submission only appends files[0] to FormData while the UI allows selecting
multiple files; either restrict the dropzone to single-file mode or append all
files to the FormData. To fix, choose one: A) restrict to single file by setting
the Upload.Dropzone prop to disable multiple selection (e.g., multiple={false})
and keep current append; or B) append every selected file by iterating files and
calling formData.append('attachments[]', file) (or with distinct keys) for each
entry and ensure the server expects an array of attachments. Ensure the UI,
input props, and backend contract are consistent.

Comment on lines +313 to +321
files={Array.from(files).map((f) => {
return {
...f,
name: f.name,
size: f.size,
extension: f.type,
removable: true
};
})}
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 | 🟡 Minor

Misleading property name: extension is assigned MIME type.

Line 318 assigns f.type (which contains the MIME type like "image/png") to a property named extension. This is misleading, as the property name suggests a file extension like ".png".

🔎 Suggested fix options

Option 1: Rename to type to reflect MIME type:

     files={Array.from(files).map((f) => {
         return {
             ...f,
             name: f.name,
             size: f.size,
-            extension: f.type,
+            type: f.type,
             removable: true
         };
     })}

Option 2: Extract actual file extension from filename:

     files={Array.from(files).map((f) => {
         return {
             ...f,
             name: f.name,
             size: f.size,
-            extension: f.type,
+            extension: f.name.split('.').pop() || '',
             removable: true
         };
     })}

Choose based on what the Upload.List component expects.

Verify what the Upload.List component expects for the file object structure:

#!/bin/bash
# Search for Upload.List component definition and its props
ast-grep --pattern 'export $_ List = $_'

# Check for type definitions or interfaces for Upload.List files prop
rg -n --type=ts -C5 'Upload\.List.*files'
🤖 Prompt for AI Agents
In src/routes/(console)/supportWizard.svelte around lines 313 to 321, the file
mapping sets extension: f.type which stores a MIME type (e.g. "image/png") but
the property name implies a file extension; fix by making the file object match
what Upload.List expects: either rename the property to type: f.type if
Upload.List expects a MIME type, or compute the actual extension from f.name
(e.g. substring after last '.' or empty string) and assign that to extension;
verify the Upload.List file prop shape and update callers/types accordingly.

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.

4 participants