fix: increase upload limit to 15MB and fix infinite loading with S3 timeouts
This commit is contained in:
@@ -58,7 +58,7 @@ export default function EditProject({ params }: { params: { id: string } }) {
|
|||||||
throw new Error('Projeto não encontrado');
|
throw new Error('Projeto não encontrado');
|
||||||
}
|
}
|
||||||
const project = await res.json();
|
const project = await res.json();
|
||||||
|
|
||||||
setFormData({
|
setFormData({
|
||||||
title: project.title || '',
|
title: project.title || '',
|
||||||
category: project.category || '',
|
category: project.category || '',
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,8 +236,8 @@ export default function EditProject({ params }: { params: { id: string } }) {
|
|||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<div className="flex items-center gap-4 mb-8">
|
<div className="flex items-center gap-4 mb-8">
|
||||||
<Link
|
<Link
|
||||||
href="/admin/projetos"
|
href="/admin/projetos"
|
||||||
className="w-10 h-10 rounded-xl bg-white dark:bg-secondary border border-gray-200 dark:border-white/10 flex items-center justify-center text-gray-500 hover:text-primary hover:border-primary transition-colors shadow-sm"
|
className="w-10 h-10 rounded-xl bg-white dark:bg-secondary border border-gray-200 dark:border-white/10 flex items-center justify-center text-gray-500 hover:text-primary hover:border-primary transition-colors shadow-sm"
|
||||||
>
|
>
|
||||||
<i className="ri-arrow-left-line text-xl"></i>
|
<i className="ri-arrow-left-line text-xl"></i>
|
||||||
@@ -255,14 +255,14 @@ export default function EditProject({ params }: { params: { id: string } }) {
|
|||||||
<i className="ri-information-line text-primary"></i>
|
<i className="ri-information-line text-primary"></i>
|
||||||
Informações Básicas
|
Informações Básicas
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Título do Projeto</label>
|
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Título do Projeto</label>
|
||||||
<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
|
||||||
@@ -271,9 +271,9 @@ export default function EditProject({ params }: { params: { id: string } }) {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<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
|
||||||
>
|
>
|
||||||
@@ -288,8 +288,8 @@ export default function EditProject({ params }: { params: { id: string } }) {
|
|||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Adicionar Nova Categoria</label>
|
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Adicionar Nova Categoria</label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={newCategory}
|
value={newCategory}
|
||||||
onChange={(e) => setNewCategory(e.target.value)}
|
onChange={(e) => setNewCategory(e.target.value)}
|
||||||
className="flex-1 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="flex-1 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"
|
||||||
@@ -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!');
|
||||||
}
|
}
|
||||||
@@ -315,10 +315,10 @@ export default function EditProject({ params }: { params: { id: string } }) {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Cliente</label>
|
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Cliente</label>
|
||||||
<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"
|
||||||
/>
|
/>
|
||||||
@@ -326,19 +326,19 @@ export default function EditProject({ params }: { params: { id: string } }) {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Data de Conclusão</label>
|
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Data de Conclusão</label>
|
||||||
<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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<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) => (
|
||||||
@@ -349,9 +349,9 @@ export default function EditProject({ params }: { params: { id: string } }) {
|
|||||||
|
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<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
|
||||||
@@ -480,14 +480,14 @@ export default function EditProject({ params }: { params: { id: string } }) {
|
|||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex items-center justify-end gap-4 pt-4">
|
<div className="flex items-center justify-end gap-4 pt-4">
|
||||||
<Link
|
<Link
|
||||||
href="/admin/projetos"
|
href="/admin/projetos"
|
||||||
className="px-8 py-3 border border-gray-200 dark:border-white/10 text-gray-600 dark:text-gray-300 rounded-xl font-bold hover:bg-gray-50 dark:hover:bg-white/5 transition-colors"
|
className="px-8 py-3 border border-gray-200 dark:border-white/10 text-gray-600 dark:text-gray-300 rounded-xl font-bold hover:bg-gray-50 dark:hover:bg-white/5 transition-colors"
|
||||||
>
|
>
|
||||||
Cancelar
|
Cancelar
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
className="px-8 py-3 bg-primary text-white rounded-xl font-bold hover-primary transition-colors shadow-lg shadow-primary/20 flex items-center gap-2 disabled:opacity-70 disabled:cursor-not-allowed"
|
className="px-8 py-3 bg-primary text-white rounded-xl font-bold hover-primary transition-colors shadow-lg shadow-primary/20 flex items-center gap-2 disabled:opacity-70 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,8 +177,8 @@ export default function NewProject() {
|
|||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<div className="flex items-center gap-4 mb-8">
|
<div className="flex items-center gap-4 mb-8">
|
||||||
<Link
|
<Link
|
||||||
href="/admin/projetos"
|
href="/admin/projetos"
|
||||||
className="w-10 h-10 rounded-xl bg-white dark:bg-secondary border border-gray-200 dark:border-white/10 flex items-center justify-center text-gray-500 hover:text-primary hover:border-primary transition-colors shadow-sm"
|
className="w-10 h-10 rounded-xl bg-white dark:bg-secondary border border-gray-200 dark:border-white/10 flex items-center justify-center text-gray-500 hover:text-primary hover:border-primary transition-colors shadow-sm"
|
||||||
>
|
>
|
||||||
<i className="ri-arrow-left-line text-xl"></i>
|
<i className="ri-arrow-left-line text-xl"></i>
|
||||||
@@ -196,14 +196,14 @@ export default function NewProject() {
|
|||||||
<i className="ri-information-line text-primary"></i>
|
<i className="ri-information-line text-primary"></i>
|
||||||
Informações Básicas
|
Informações Básicas
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Título do Projeto</label>
|
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Título do Projeto</label>
|
||||||
<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
|
||||||
@@ -212,9 +212,9 @@ export default function NewProject() {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<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
|
||||||
>
|
>
|
||||||
@@ -229,8 +229,8 @@ export default function NewProject() {
|
|||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Adicionar Nova Categoria</label>
|
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Adicionar Nova Categoria</label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={newCategory}
|
value={newCategory}
|
||||||
onChange={(e) => setNewCategory(e.target.value)}
|
onChange={(e) => setNewCategory(e.target.value)}
|
||||||
className="flex-1 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="flex-1 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"
|
||||||
@@ -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!');
|
||||||
}
|
}
|
||||||
@@ -256,10 +256,10 @@ export default function NewProject() {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Cliente</label>
|
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Cliente</label>
|
||||||
<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"
|
||||||
/>
|
/>
|
||||||
@@ -267,19 +267,19 @@ export default function NewProject() {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Data de Conclusão</label>
|
<label className="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Data de Conclusão</label>
|
||||||
<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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<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) => (
|
||||||
@@ -290,9 +290,9 @@ export default function NewProject() {
|
|||||||
|
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<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
|
||||||
@@ -421,14 +421,14 @@ export default function NewProject() {
|
|||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex items-center justify-end gap-4 pt-4">
|
<div className="flex items-center justify-end gap-4 pt-4">
|
||||||
<Link
|
<Link
|
||||||
href="/admin/projetos"
|
href="/admin/projetos"
|
||||||
className="px-8 py-3 border border-gray-200 dark:border-white/10 text-gray-600 dark:text-gray-300 rounded-xl font-bold hover:bg-gray-50 dark:hover:bg-white/5 transition-colors"
|
className="px-8 py-3 border border-gray-200 dark:border-white/10 text-gray-600 dark:text-gray-300 rounded-xl font-bold hover:bg-gray-50 dark:hover:bg-white/5 transition-colors"
|
||||||
>
|
>
|
||||||
Cancelar
|
Cancelar
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
className="px-8 py-3 bg-primary text-white rounded-xl font-bold hover-primary transition-colors shadow-lg shadow-primary/20 flex items-center gap-2 disabled:opacity-70 disabled:cursor-not-allowed"
|
className="px-8 py-3 bg-primary text-white rounded-xl font-bold hover-primary transition-colors shadow-lg shadow-primary/20 flex items-center gap-2 disabled:opacity-70 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
@@ -15,7 +25,7 @@ export async function POST(request: Request) {
|
|||||||
|
|
||||||
const buffer = Buffer.from(await file.arrayBuffer());
|
const buffer = Buffer.from(await file.arrayBuffer());
|
||||||
const filename = `${uuidv4()}-${file.name.replace(/\s+/g, '-')}`; // Sanitize filename
|
const filename = `${uuidv4()}-${file.name.replace(/\s+/g, '-')}`; // Sanitize filename
|
||||||
|
|
||||||
await minioClient.putObject(bucketName, filename, buffer, file.size, {
|
await minioClient.putObject(bucketName, filename, buffer, file.size, {
|
||||||
'Content-Type': file.type,
|
'Content-Type': file.type,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user