diff --git a/src/lib/components/TitanSupport.svelte b/src/lib/components/TitanSupport.svelte index c6ddcbef223..f35a8ce5a33 100644 --- a/src/lib/components/TitanSupport.svelte +++ b/src/lib/components/TitanSupport.svelte @@ -21,6 +21,7 @@ file: File; preview: string; name: string; + size: number; } interface OAuthTokenResponse { @@ -32,7 +33,8 @@ let requestType: RequestType = 'issue'; let description = ''; const maxLength = 2000; - const maxFiles = 20; + const maxFiles = 10; + const maxFileSizeBytes = 2 * 1024 * 1024; // 2MB per file const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg']; let supportID: string = ''; @@ -49,7 +51,7 @@ let dragCounter = 0; let pasteSuccess = false; - // ─── SUCCESS MODAL (only addition) ──────────────────────────────────────── + // ─── SUCCESS MODAL ──────────────────────────────────────────────────────── let showSuccessModal = false; function handleSuccessClose(): void { @@ -57,7 +59,14 @@ open = false; onClose(); } - // ────────────────────────────────────────────────────────────────────────── + + // ─── Helpers ────────────────────────────────────────────────────────────── + + function formatBytes(bytes: number): string { + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; + return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; + } const requestTypeMap: Record = { issue: 'issue', @@ -103,7 +112,6 @@ } } - // Fetch token whenever dialog opens (and we don't already have one) $: if (open && !bearerToken) { fetchBearerToken(); } @@ -191,20 +199,39 @@ function validateAndFilter(files: File[]): File[] { fileErrorMessage = ''; + const errors: string[] = []; - const invalid = files.filter((f) => !allowedTypes.includes(f.type)); - if (invalid.length > 0) { - fileErrorMessage = `Invalid file type${invalid.length > 1 ? 's' : ''}: only PNG, JPG and JPEG are allowed.`; + // 1. Type check + const invalidType = files.filter((f) => !allowedTypes.includes(f.type)); + if (invalidType.length > 0) { + errors.push( + `Invalid file type${invalidType.length > 1 ? 's' : ''}: only PNG, JPG and JPEG are allowed.` + ); } + let valid = files.filter((f) => allowedTypes.includes(f.type)); + + // 2. Per-file size check (max 2MB each) + const tooLarge = valid.filter((f) => f.size > maxFileSizeBytes); + if (tooLarge.length > 0) { + const names = tooLarge.map((f) => `"${f.name}" (${formatBytes(f.size)})`).join(', '); + errors.push( + `${tooLarge.length} file${tooLarge.length > 1 ? 's exceed' : ' exceeds'} the ${formatBytes(maxFileSizeBytes)} per-file limit and ${tooLarge.length > 1 ? 'were' : 'was'} skipped: ${names}.` + ); + } + valid = valid.filter((f) => f.size <= maxFileSizeBytes); - const valid = files.filter((f) => allowedTypes.includes(f.type)); + // 3. File count check (max 10) const remaining = maxFiles - uploadedFiles.length; - if (valid.length > remaining) { - fileErrorMessage = `Maximum ${maxFiles} screenshots allowed. ${valid.length - remaining} file${ - valid.length - remaining > 1 ? 's were' : ' was' - } skipped.`; - return valid.slice(0, remaining); + const skipped = valid.length - remaining; + errors.push( + `Maximum ${maxFiles} screenshots allowed. ${skipped} file${skipped > 1 ? 's were' : ' was'} skipped.` + ); + valid = valid.slice(0, remaining); + } + + if (errors.length > 0) { + fileErrorMessage = errors.join(' '); } return valid; @@ -289,7 +316,8 @@ id: Date.now() + Math.random(), file, preview: e.target?.result as string, - name: file.name + name: file.name, + size: file.size } ]; }; @@ -311,9 +339,8 @@ } if (!bearerToken) { - // Try re-fetching token if we don't have one await fetchBearerToken(); - if (!bearerToken) return; // fetchBearerToken sets errorMessage + if (!bearerToken) return; } isSubmitting = true; @@ -323,7 +350,7 @@ const formData = new FormData(); formData.append('request_type', requestTypeMap[requestType]); formData.append('description', description.trim()); - formData.append("email", $user?.email || ""); + formData.append('email', $user?.email || ''); uploadedFiles.forEach((fileObj, index) => { formData.append(`attachments_attributes[${index}][attachment_file]`, fileObj.file); @@ -387,9 +414,7 @@ pasteSuccess = false; open = false; - // ─── Show success modal instead of alert (only change here) ─────────── showSuccessModal = true; - // ────────────────────────────────────────────────────────────────────── } catch (error) { console.error('Error submitting support request:', error); errorMessage = @@ -414,7 +439,6 @@
-
@@ -439,7 +463,8 @@
- Ticket ID: {supportID} + Ticket ID: + {supportID}
-

- Max 20 files · PNG, JPG only + + +

+ Max {maxFiles} files · PNG, JPG only · {formatBytes(maxFileSizeBytes)} per file + + · {uploadedFiles.length}/{maxFiles} uploaded +

+ {#if fileErrorMessage} -

+

- {fileErrorMessage} -

+

+ {fileErrorMessage} +

+
{/if} @@ -782,9 +815,10 @@ ✕
- {file.name} +
{file.name}
+
{formatBytes(file.size)}
{/each} @@ -792,7 +826,7 @@ {/if} - + {#if errorMessage}
+ \ No newline at end of file