From 0c40dbadfcf0449197d0695a8732360a4e10a8d9 Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 7 Mar 2026 18:04:56 -0300 Subject: [PATCH] feat: implement cms backup (export/import) and admin interface --- frontend/src/app/admin/backup/page.tsx | 185 +++++++++++++++++++++ frontend/src/app/admin/layout.tsx | 21 +-- frontend/src/app/api/admin/backup/route.ts | 133 +++++++++++++++ 3 files changed, 329 insertions(+), 10 deletions(-) create mode 100644 frontend/src/app/admin/backup/page.tsx create mode 100644 frontend/src/app/api/admin/backup/route.ts diff --git a/frontend/src/app/admin/backup/page.tsx b/frontend/src/app/admin/backup/page.tsx new file mode 100644 index 0000000..07b9cfc --- /dev/null +++ b/frontend/src/app/admin/backup/page.tsx @@ -0,0 +1,185 @@ +"use client"; + +import { useState } from 'react'; +import { useToast } from '@/contexts/ToastContext'; +import { useConfirm } from '@/contexts/ConfirmContext'; + +export default function BackupPage() { + const [isExporting, setIsExporting] = useState(false); + const [isImporting, setIsImporting] = useState(false); + const { success, error } = useToast(); + const { confirm } = useConfirm(); + + const handleExport = async () => { + setIsExporting(true); + try { + const response = await fetch('/api/admin/backup'); + if (!response.ok) throw new Error('Falha ao exportar backup'); + + const data = await response.json(); + const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `backup-occto-cms-${new Date().toISOString().split('T')[0]}.json`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + + success('Backup exportado com sucesso!'); + } catch (err) { + console.error(err); + error('Erro ao exportar backup. Tente novamente.'); + } finally { + setIsExporting(false); + } + }; + + const handleImport = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + const confirmed = await confirm({ + title: 'Confirmar Importação', + message: 'Isso irá substituir ou atualizar os dados existentes (Projetos, Serviços, Páginas e Configurações). Deseja continuar?', + confirmText: 'Importar', + cancelText: 'Cancelar', + type: 'warning' + }); + + if (!confirmed) { + e.target.value = ''; + return; + } + + setIsImporting(true); + try { + const reader = new FileReader(); + reader.onload = async (event) => { + try { + const content = event.target?.result as string; + const backupData = JSON.parse(content); + + const response = await fetch('/api/admin/backup', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(backupData), + }); + + if (response.ok) { + success('Backup importado com sucesso!'); + // Opcional: recarregar a página para ver mudanças + setTimeout(() => window.location.reload(), 1500); + } else { + const data = await response.json(); + error(data.error || 'Erro ao importar backup'); + } + } catch (err) { + error('Arquivo de backup inválido.'); + } finally { + setIsImporting(false); + } + }; + reader.readAsText(file); + } catch (err) { + error('Erro ao ler arquivo.'); + setIsImporting(false); + } + + e.target.value = ''; + }; + + return ( +
+
+

Backup do Sistema

+

+ Gerencie a segurança dos seus dados. Você pode exportar todos os conteúdos do CMS para um arquivo local ou restaurar um backup anterior. +

+
+ +
+ {/* Export Card */} +
+
+ +
+

Exportar Dados

+

+ Cria um arquivo JSON contendo todos os projetos, serviços, textos de páginas e configurações globais. Ideal para salvar o progresso atual. +

+ +
+ + {/* Import Card */} +
+
+ +
+

Restaurar Backup

+

+ Importe um arquivo de backup previamente exportado. Atenção: Isso irá substituir ou atualizar os dados existentes. +

+ +
+
+ + {/* Info Section */} +
+

+ + O que está incluído no backup? +

+
    + {[ + 'Todos os Projetos Detalhados', + 'Lista de Serviços e Descrições', + 'Textos e Traduções de Todas as Páginas', + 'Configurações Globais (Redes Sociais, Contato)', + 'Estrutura de Categorias e Status', + 'Referências de Imagens (URLs)' + ].map((item, i) => ( +
  • + + {item} +
  • + ))} +
+
+

+ + + Nota importante: O backup contém os links das imagens. Se as imagens originais forem deletadas do servidor, elas não aparecerão mesmo restaurando o backup. + +

+
+
+
+ ); +} diff --git a/frontend/src/app/admin/layout.tsx b/frontend/src/app/admin/layout.tsx index 7dfaab4..d4a4817 100644 --- a/frontend/src/app/admin/layout.tsx +++ b/frontend/src/app/admin/layout.tsx @@ -251,6 +251,7 @@ export default function AdminLayout({ { icon: 'ri-pages-line', label: 'Páginas', href: '/admin/paginas' }, { icon: 'ri-message-3-line', label: 'Mensagens', href: '/admin/mensagens' }, { icon: 'ri-user-settings-line', label: 'Usuários', href: '/admin/usuarios' }, + { icon: 'ri-database-2-line', label: 'Backup', href: '/admin/backup' }, { icon: 'ri-settings-3-line', label: 'Configurações', href: '/admin/configuracoes' }, ]; @@ -263,11 +264,11 @@ export default function AdminLayout({
{logo ? ( - OCCTO Engenharia @@ -289,8 +290,8 @@ export default function AdminLayout({ {menuItems.map((item) => { const isActive = pathname === item.href; return ( - @@ -302,7 +303,7 @@ export default function AdminLayout({
-