Skip to content

Commit 84c7de8

Browse files
pinkhoodieMananTank
authored andcommitted
fix(nebula): consolidate image upload validation and enhance UX - Move all file validation logic to handleImageUpload function as requested - Add consistent validation to onDrop and onPaste handlers - Enhance drag-and-drop UX with visual feedback and state management - Add accessibility attributes (aria-dropeffect) for better screen reader support - Remove duplicate validation from ImageUploadButton to avoid redundancy - Preserve existing validation behavior (5MB file size limit) - Addresses CodeRabbit suggestions and Manan's feedback for consistent file validation across all upload methods
1 parent b217f90 commit 84c7de8

File tree

1 file changed

+54
-30
lines changed
  • apps/dashboard/src/app/nebula-app/(app)/components

1 file changed

+54
-30
lines changed

apps/dashboard/src/app/nebula-app/(app)/components/ChatBar.tsx

Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export function ChatBar(props: {
7676
const [images, setImages] = useState<
7777
Array<{ file: File; b64: string | undefined }>
7878
>([]);
79+
const [isDragOver, setIsDragOver] = useState(false);
7980

8081
function handleSubmit(message: string) {
8182
const userMessage: NebulaUserMessage = {
@@ -104,10 +105,35 @@ export function ChatBar(props: {
104105
},
105106
});
106107

107-
async function handleImageUpload(images: File[]) {
108+
async function handleImageUpload(files: File[]) {
109+
const totalFiles = files.length + images.length;
110+
111+
if (totalFiles > maxAllowedImagesPerMessage) {
112+
toast.error(
113+
`You can only upload up to ${maxAllowedImagesPerMessage} images at a time`,
114+
{
115+
position: "top-right",
116+
}
117+
);
118+
return;
119+
}
120+
121+
const validFiles: File[] = [];
122+
123+
for (const file of files) {
124+
if (file.size <= 5 * 1024 * 1024) {
125+
validFiles.push(file);
126+
} else {
127+
toast.error("Image is larger than 5MB", {
128+
description: `File: ${file.name}`,
129+
position: "top-right",
130+
});
131+
}
132+
}
133+
108134
try {
109135
const urls = await Promise.all(
110-
images.map(async (image) => {
136+
validFiles.map(async (image) => {
111137
const b64 = await uploadImageMutation.mutateAsync(image);
112138
return { file: image, b64: b64 };
113139
})
@@ -126,16 +152,39 @@ export function ChatBar(props: {
126152
<DynamicHeight transition="height 200ms ease">
127153
<div
128154
className={cn(
129-
"overflow-hidden rounded-2xl border border-border bg-card",
155+
"overflow-hidden rounded-2xl border border-border bg-card transition-colors",
156+
isDragOver &&
157+
props.allowImageUpload &&
158+
"border-nebula-pink-foreground bg-nebula-pink/5",
130159
props.className
131160
)}
132161
onDrop={(e) => {
133162
e.preventDefault();
163+
setIsDragOver(false);
134164
if (!props.allowImageUpload) return;
135165
const files = Array.from(e.dataTransfer.files);
136166
if (files.length > 0) handleImageUpload(files);
137167
}}
138-
onDragOver={(e) => e.preventDefault()}
168+
onDragOver={(e) => {
169+
e.preventDefault();
170+
if (props.allowImageUpload) {
171+
setIsDragOver(true);
172+
}
173+
}}
174+
onDragEnter={(e) => {
175+
e.preventDefault();
176+
if (props.allowImageUpload) {
177+
setIsDragOver(true);
178+
}
179+
}}
180+
onDragLeave={(e) => {
181+
e.preventDefault();
182+
// Only set drag over to false if we're leaving the container entirely
183+
if (!e.currentTarget.contains(e.relatedTarget as Node)) {
184+
setIsDragOver(false);
185+
}
186+
}}
187+
aria-dropeffect={props.allowImageUpload ? "copy" : "none"}
139188
>
140189
{images.length > 0 && (
141190
<ImagePreview
@@ -274,32 +323,7 @@ export function ChatBar(props: {
274323
value={undefined}
275324
accept="image/jpeg,image/png,image/webp"
276325
onChange={(files) => {
277-
const totalFiles = files.length + images.length;
278-
279-
if (totalFiles > maxAllowedImagesPerMessage) {
280-
toast.error(
281-
`You can only upload up to ${maxAllowedImagesPerMessage} images at a time`,
282-
{
283-
position: "top-right",
284-
}
285-
);
286-
return;
287-
}
288-
289-
const validFiles: File[] = [];
290-
291-
for (const file of files) {
292-
if (file.size <= 5 * 1024 * 1024) {
293-
validFiles.push(file);
294-
} else {
295-
toast.error("Image is larger than 5MB", {
296-
description: `File: ${file.name}`,
297-
position: "top-right",
298-
});
299-
}
300-
}
301-
302-
handleImageUpload(validFiles);
326+
handleImageUpload(files);
303327
}}
304328
variant="ghost"
305329
className="!h-auto w-auto shrink-0 gap-2 p-2"

0 commit comments

Comments
 (0)