fix: increase upload limit to 15MB and fix infinite loading with S3 timeouts

This commit is contained in:
Erik
2026-03-09 20:14:35 -03:00
parent 4f27f501b9
commit 6cd5d490de
4 changed files with 72 additions and 56 deletions

View File

@@ -98,8 +98,8 @@ export default function EditProject({ params }: { params: { id: string } }) {
}, [params.id, error, router]); }, [params.id, error, router]);
const uploadFile = async (file: File): Promise<UploadedImage | null> => { const uploadFile = async (file: File): Promise<UploadedImage | null> => {
if (file.size > 2 * 1024 * 1024) { if (file.size > 15 * 1024 * 1024) {
error('Arquivo maior que 2MB. Escolha uma imagem menor.'); error('Arquivo maior que 15MB. Escolha uma imagem menor.');
return null; return null;
} }
@@ -262,7 +262,7 @@ export default function EditProject({ params }: { params: { id: string } }) {
<input <input
type="text" type="text"
value={formData.title} value={formData.title}
onChange={(e) => setFormData({...formData, title: e.target.value})} onChange={(e) => setFormData({ ...formData, title: e.target.value })}
className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all" className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all"
placeholder="Ex: Adequação de Frota Coca-Cola" placeholder="Ex: Adequação de Frota Coca-Cola"
required required
@@ -273,7 +273,7 @@ export default function EditProject({ params }: { params: { id: string } }) {
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Categoria</label> <label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Categoria</label>
<select <select
value={formData.category} value={formData.category}
onChange={(e) => setFormData({...formData, category: e.target.value})} onChange={(e) => setFormData({ ...formData, category: e.target.value })}
className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all appearance-none cursor-pointer" className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all appearance-none cursor-pointer"
required required
> >
@@ -301,7 +301,7 @@ export default function EditProject({ params }: { params: { id: string } }) {
if (newCategory.trim()) { if (newCategory.trim()) {
const newCat = { value: newCategory, label: newCategory }; const newCat = { value: newCategory, label: newCategory };
setCategories([...categories, newCat]); setCategories([...categories, newCat]);
setFormData({...formData, category: newCategory}); setFormData({ ...formData, category: newCategory });
setNewCategory(''); setNewCategory('');
success('Categoria adicionada!'); success('Categoria adicionada!');
} }
@@ -318,7 +318,7 @@ export default function EditProject({ params }: { params: { id: string } }) {
<input <input
type="text" type="text"
value={formData.client} value={formData.client}
onChange={(e) => setFormData({...formData, client: e.target.value})} onChange={(e) => setFormData({ ...formData, client: e.target.value })}
className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all" className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all"
placeholder="Ex: Coca-Cola FEMSA" placeholder="Ex: Coca-Cola FEMSA"
/> />
@@ -329,7 +329,7 @@ export default function EditProject({ params }: { params: { id: string } }) {
<input <input
type="date" type="date"
value={formData.date} value={formData.date}
onChange={(e) => setFormData({...formData, date: e.target.value})} onChange={(e) => setFormData({ ...formData, date: e.target.value })}
className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all" className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all"
/> />
</div> </div>
@@ -338,7 +338,7 @@ export default function EditProject({ params }: { params: { id: string } }) {
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Status</label> <label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Status</label>
<select <select
value={formData.status} value={formData.status}
onChange={(e) => setFormData({...formData, status: e.target.value})} onChange={(e) => setFormData({ ...formData, status: e.target.value })}
className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all appearance-none cursor-pointer" className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all appearance-none cursor-pointer"
> >
{STATUS_OPTIONS.map((option) => ( {STATUS_OPTIONS.map((option) => (
@@ -351,7 +351,7 @@ export default function EditProject({ params }: { params: { id: string } }) {
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Descrição Detalhada</label> <label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Descrição Detalhada</label>
<textarea <textarea
value={formData.description} value={formData.description}
onChange={(e) => setFormData({...formData, description: e.target.value})} onChange={(e) => setFormData({ ...formData, description: e.target.value })}
rows={6} rows={6}
className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all resize-none" className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all resize-none"
placeholder="Descreva os detalhes técnicos, desafios e soluções do projeto..." placeholder="Descreva os detalhes técnicos, desafios e soluções do projeto..."
@@ -413,7 +413,7 @@ export default function EditProject({ params }: { params: { id: string } }) {
<p className="text-gray-600 dark:text-gray-300 font-medium mb-1"> <p className="text-gray-600 dark:text-gray-300 font-medium mb-1">
{uploadingCover ? 'Enviando imagem...' : 'Clique para fazer upload ou arraste e solte'} {uploadingCover ? 'Enviando imagem...' : 'Clique para fazer upload ou arraste e solte'}
</p> </p>
<p className="text-xs text-gray-400">PNG, JPG ou WEBP (máximo 2MB)</p> <p className="text-xs text-gray-400">PNG, JPG ou WEBP (máximo 15MB)</p>
</div> </div>
)} )}
<input <input

View File

@@ -49,8 +49,8 @@ export default function NewProject() {
const isSaving = loading || uploadingCover || uploadingGallery; const isSaving = loading || uploadingCover || uploadingGallery;
const uploadFile = async (file: File): Promise<UploadedImage | null> => { const uploadFile = async (file: File): Promise<UploadedImage | null> => {
if (file.size > 2 * 1024 * 1024) { if (file.size > 15 * 1024 * 1024) {
error('Arquivo maior que 2MB. Escolha uma imagem menor.'); error('Arquivo maior que 15MB. Escolha uma imagem menor.');
return null; return null;
} }
@@ -203,7 +203,7 @@ export default function NewProject() {
<input <input
type="text" type="text"
value={formData.title} value={formData.title}
onChange={(e) => setFormData({...formData, title: e.target.value})} onChange={(e) => setFormData({ ...formData, title: e.target.value })}
className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all" className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all"
placeholder="Ex: Adequação de Frota Coca-Cola" placeholder="Ex: Adequação de Frota Coca-Cola"
required required
@@ -214,7 +214,7 @@ export default function NewProject() {
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Categoria</label> <label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Categoria</label>
<select <select
value={formData.category} value={formData.category}
onChange={(e) => setFormData({...formData, category: e.target.value})} onChange={(e) => setFormData({ ...formData, category: e.target.value })}
className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all appearance-none cursor-pointer" className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all appearance-none cursor-pointer"
required required
> >
@@ -242,7 +242,7 @@ export default function NewProject() {
if (newCategory.trim()) { if (newCategory.trim()) {
const newCat = { value: newCategory, label: newCategory }; const newCat = { value: newCategory, label: newCategory };
setCategories([...categories, newCat]); setCategories([...categories, newCat]);
setFormData({...formData, category: newCategory}); setFormData({ ...formData, category: newCategory });
setNewCategory(''); setNewCategory('');
success('Categoria adicionada!'); success('Categoria adicionada!');
} }
@@ -259,7 +259,7 @@ export default function NewProject() {
<input <input
type="text" type="text"
value={formData.client} value={formData.client}
onChange={(e) => setFormData({...formData, client: e.target.value})} onChange={(e) => setFormData({ ...formData, client: e.target.value })}
className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all" className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all"
placeholder="Ex: Coca-Cola FEMSA" placeholder="Ex: Coca-Cola FEMSA"
/> />
@@ -270,7 +270,7 @@ export default function NewProject() {
<input <input
type="date" type="date"
value={formData.date} value={formData.date}
onChange={(e) => setFormData({...formData, date: e.target.value})} onChange={(e) => setFormData({ ...formData, date: e.target.value })}
className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all" className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all"
/> />
</div> </div>
@@ -279,7 +279,7 @@ export default function NewProject() {
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Status</label> <label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Status</label>
<select <select
value={formData.status} value={formData.status}
onChange={(e) => setFormData({...formData, status: e.target.value})} onChange={(e) => setFormData({ ...formData, status: e.target.value })}
className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all appearance-none cursor-pointer" className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all appearance-none cursor-pointer"
> >
{STATUS_OPTIONS.map((option) => ( {STATUS_OPTIONS.map((option) => (
@@ -292,7 +292,7 @@ export default function NewProject() {
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Descrição Detalhada</label> <label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Descrição Detalhada</label>
<textarea <textarea
value={formData.description} value={formData.description}
onChange={(e) => setFormData({...formData, description: e.target.value})} onChange={(e) => setFormData({ ...formData, description: e.target.value })}
rows={6} rows={6}
className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all resize-none" className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all resize-none"
placeholder="Descreva os detalhes técnicos, desafios e soluções do projeto..." placeholder="Descreva os detalhes técnicos, desafios e soluções do projeto..."
@@ -354,7 +354,7 @@ export default function NewProject() {
<p className="text-gray-600 dark:text-gray-300 font-medium mb-1"> <p className="text-gray-600 dark:text-gray-300 font-medium mb-1">
{uploadingCover ? 'Enviando imagem...' : 'Clique para fazer upload ou arraste e solte'} {uploadingCover ? 'Enviando imagem...' : 'Clique para fazer upload ou arraste e solte'}
</p> </p>
<p className="text-xs text-gray-400">PNG, JPG ou WEBP (máximo 2MB)</p> <p className="text-xs text-gray-400">PNG, JPG ou WEBP (máximo 15MB)</p>
</div> </div>
)} )}
<input <input

View File

@@ -2,7 +2,17 @@ import { NextResponse } from 'next/server';
import { minioClient, bucketName, ensureBucketExists } from '@/lib/minio'; import { minioClient, bucketName, ensureBucketExists } from '@/lib/minio';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
export const maxDuration = 60; // 60 segundos
export const config = {
api: {
bodyParser: {
sizeLimit: '15mb',
},
},
};
export async function POST(request: Request) { export async function POST(request: Request) {
console.log('📤 Recebendo upload de arquivo...');
try { try {
await ensureBucketExists(); await ensureBucketExists();

View File

@@ -15,6 +15,8 @@ console.log(`[S3/MinIO] Configurando cliente: ${endpointUrl} (SSL: ${useSSL})`);
export const bucketName = process.env.S3_BUCKET_NAME || process.env.MINIO_BUCKET_NAME || 'occto-images'; export const bucketName = process.env.S3_BUCKET_NAME || process.env.MINIO_BUCKET_NAME || 'occto-images';
import { NodeHttpHandler } from '@smithy/node-http-handler';
export const s3Client = new S3Client({ export const s3Client = new S3Client({
region: 'us-east-1', region: 'us-east-1',
endpoint: endpointUrl, endpoint: endpointUrl,
@@ -23,6 +25,10 @@ export const s3Client = new S3Client({
accessKeyId: process.env.S3_ACCESS_KEY || process.env.MINIO_ACCESS_KEY || 'admin', accessKeyId: process.env.S3_ACCESS_KEY || process.env.MINIO_ACCESS_KEY || 'admin',
secretAccessKey: process.env.S3_SECRET_KEY || process.env.MINIO_SECRET_KEY || 'adminpassword', secretAccessKey: process.env.S3_SECRET_KEY || process.env.MINIO_SECRET_KEY || 'adminpassword',
}, },
requestHandler: new NodeHttpHandler({
connectionTimeout: 10000, // 10 segundos
socketTimeout: 10000, // 10 segundos
}),
}); });
type MetadataMap = Record<string, string> | undefined; type MetadataMap = Record<string, string> | undefined;