diff --git a/app/Http/Requests/StoreSupportConversationRequest.php b/app/Http/Requests/StoreSupportConversationRequest.php index bb1038e..1912de7 100644 --- a/app/Http/Requests/StoreSupportConversationRequest.php +++ b/app/Http/Requests/StoreSupportConversationRequest.php @@ -3,6 +3,7 @@ namespace App\Http\Requests; use App\Models\SupportConversation; +use App\Rules\SupportAttachmentFile; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; @@ -23,7 +24,7 @@ class StoreSupportConversationRequest extends FormRequest 'subject' => ['required', 'string', 'max:120'], 'body' => ['required', 'string', 'max:2000'], 'attachments' => ['nullable', 'array', 'max:3'], - 'attachments.*' => ['file', 'max:10240', 'mimes:jpg,jpeg,png,webp,gif,pdf,txt,log,json,zip'], + 'attachments.*' => [new SupportAttachmentFile], ]; } } diff --git a/app/Http/Requests/StoreSupportMessageRequest.php b/app/Http/Requests/StoreSupportMessageRequest.php index b419abb..4648dc6 100644 --- a/app/Http/Requests/StoreSupportMessageRequest.php +++ b/app/Http/Requests/StoreSupportMessageRequest.php @@ -2,6 +2,7 @@ namespace App\Http\Requests; +use App\Rules\SupportAttachmentFile; use Illuminate\Foundation\Http\FormRequest; class StoreSupportMessageRequest extends FormRequest @@ -12,14 +13,14 @@ class StoreSupportMessageRequest extends FormRequest } /** - * @return array> + * @return array> */ public function rules(): array { return [ 'body' => ['required', 'string', 'max:2000'], 'attachments' => ['nullable', 'array', 'max:3'], - 'attachments.*' => ['file', 'max:10240', 'mimes:jpg,jpeg,png,webp,gif,pdf,txt,log,json,zip'], + 'attachments.*' => [new SupportAttachmentFile], ]; } } diff --git a/app/Rules/SupportAttachmentFile.php b/app/Rules/SupportAttachmentFile.php new file mode 100644 index 0000000..422e31c --- /dev/null +++ b/app/Rules/SupportAttachmentFile.php @@ -0,0 +1,77 @@ + $value], + [ + 'attachment' => [ + 'file', + 'max:'.self::MAX_UPLOAD_KILOBYTES, + 'mimetypes:'.implode(',', self::ALLOWED_MIME_TYPES), + ], + ], + [ + 'attachment.file' => 'Attachment upload failed. Try the file again or choose a different file.', + 'attachment.max' => 'Attachments must be 200 MB or smaller.', + 'attachment.mimetypes' => 'Unsupported attachment type. Upload media, PDF, text/data, Office, OpenDocument, or archive files.', + ], + ); + + if ($validator->fails()) { + $fail($validator->errors()->first('attachment')); + } + } +} diff --git a/resources/js/components/support-attachment-errors.tsx b/resources/js/components/support-attachment-errors.tsx new file mode 100644 index 0000000..24e1dea --- /dev/null +++ b/resources/js/components/support-attachment-errors.tsx @@ -0,0 +1,28 @@ +type Props = { + errors: Partial>; +}; + +export function SupportAttachmentErrors({ errors }: Props) { + const messages = Object.entries(errors) + .filter( + ([key, message]) => + Boolean(message) && + (key === 'attachments' || key.startsWith('attachments.')), + ) + .map(([key, message]) => ({ + key, + message: message as string, + })); + + if (messages.length === 0) { + return null; + } + + return ( +
+ {messages.map((item) => ( +
{item.message}
+ ))} +
+ ); +} diff --git a/resources/js/pages/admin/support/show.tsx b/resources/js/pages/admin/support/show.tsx index eb5ecf4..e324ad2 100644 --- a/resources/js/pages/admin/support/show.tsx +++ b/resources/js/pages/admin/support/show.tsx @@ -11,6 +11,7 @@ import { } from 'lucide-react'; import type { FormEvent, ReactNode } from 'react'; import { useRef } from 'react'; +import { SupportAttachmentErrors } from '@/components/support-attachment-errors'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; @@ -142,10 +143,7 @@ export default function AdminSupportShow({ conversation }: Props) { maxLength={2000} /> - + )} +