fix: settings save button contrast, modal confirmation, Asaas status test, and prominent activity question creation buttons
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 43s Details

This commit is contained in:
Sidney Gomes 2026-07-20 21:08:08 -03:00
parent bd9403a872
commit c4af3c053b
4 changed files with 159 additions and 8 deletions

View File

@ -637,6 +637,15 @@ app.post('/api/admin/settings', authenticateToken, requireAdmin, (req: Authentic
res.json({ success: true, settings: db.settings });
});
app.get('/api/admin/test-asaas', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => {
try {
const result = await AsaasService.testConnection();
res.json(result);
} catch (err: any) {
res.status(500).json({ success: false, message: err.message || 'Erro ao testar conexão Asaas' });
}
});
// Admin manually unlock or lock a course for a student
app.post('/api/admin/students/:id/unlock-course', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => {
const { courseId, unlocked } = req.body;

View File

@ -392,6 +392,21 @@ export default function AdminContentManager({ token }: AdminContentManagerProps)
)}
</div>
{/* Helper Banner for Questionnaires / Activities */}
{!selectedCourse && !showCourseForm && (
<div className="glass-card p-4 rounded-xl border border-emerald-500/30 bg-emerald-950/20 text-emerald-300 text-xs leading-relaxed flex items-center space-x-3">
<div className="p-2.5 bg-emerald-500/20 text-emerald-400 rounded-xl flex-shrink-0">
<CheckCircle className="w-6 h-6" />
</div>
<div className="space-y-1">
<p className="font-extrabold text-white text-sm">Como Criar as Questões e Atividades dos Alunos?</p>
<p className="text-zinc-300">
Escolha um curso abaixo clicando no botão <strong className="text-white bg-white/10 px-1.5 py-0.5 rounded">Gerenciar Módulos & Questões</strong>. Em seguida, em cada módulo, clique no botão verde <strong className="text-emerald-300 bg-emerald-500/30 border border-emerald-400/40 px-2 py-0.5 rounded">📝 Criar / Editar Questões</strong> para adicionar perguntas e respostas!
</p>
</div>
</div>
)}
{/* --- RENDER COURSE FORM --- */}
{showCourseForm && (
<div className="glass-card p-6 rounded-2xl space-y-6">
@ -698,13 +713,14 @@ export default function AdminContentManager({ token }: AdminContentManagerProps)
<span>Nova Aula</span>
</button>
{/* Manage Activity */}
{/* Manage Activity / Questions */}
<button
onClick={() => handleManageActivity(mod.id)}
className="bg-zinc-800 text-zinc-300 hover:text-white hover:bg-zinc-700 text-[10px] font-bold px-2 py-1 rounded flex items-center space-x-1 border border-zinc-700 transition-colors"
className="bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-extrabold px-3 py-1.5 rounded-lg flex items-center space-x-1.5 border border-emerald-400/40 transition-all shadow-[0_0_12px_rgba(16,185,129,0.35)] cursor-pointer"
title="Criar ou editar o questionário deste módulo"
>
<CheckCircle className="w-3 h-3 text-emerald-500" />
<span>Gerenciar Atividade</span>
<CheckCircle className="w-4 h-4 text-white" />
<span>📝 Criar / Editar Questões</span>
</button>
{/* Edit Module */}
@ -803,9 +819,10 @@ export default function AdminContentManager({ token }: AdminContentManagerProps)
<div className="p-5 border-t border-white/5 bg-white/3 flex items-center justify-between">
<button
onClick={() => handleSelectCourse(course)}
className="glass-btn-secondary text-white text-xs font-bold px-3 py-2 rounded flex items-center space-x-1 cursor-pointer"
className="glass-btn-primary text-white text-xs font-bold px-3.5 py-2 rounded-lg flex items-center space-x-1.5 cursor-pointer shadow-md hover:scale-105 transition-all"
>
<span>Grade Curricular</span>
<Layers className="w-4 h-4 text-white" />
<span>Gerenciar Módulos & Questões</span>
<ChevronRight className="w-3.5 h-3.5" />
</button>

View File

@ -71,6 +71,8 @@ export default function AdminDashboard({ token }: AdminDashboardProps) {
// Settings State
const [settings, setSettings] = useState<any>(null);
const [isSavingSettings, setIsSavingSettings] = useState(false);
const [showSaveSuccessModal, setShowSaveSuccessModal] = useState(false);
const [asaasTestResult, setAsaasTestResult] = useState<{ loading: boolean; success: boolean | null; message: string } | null>(null);
useEffect(() => {
fetchDashboardData();
@ -106,17 +108,39 @@ export default function AdminDashboard({ token }: AdminDashboardProps) {
body: JSON.stringify(settings)
});
if (res.ok) {
alert('Configurações salvas com sucesso!');
setShowSaveSuccessModal(true);
} else {
alert('Erro ao salvar configurações.');
}
} catch (err) {
console.error('Error saving settings:', err);
alert('Erro de conexão ao salvar configurações.');
} finally {
setIsSavingSettings(false);
}
};
const handleTestAsaasConnection = async () => {
setAsaasTestResult({ loading: true, success: null, message: 'Testando conexão com Asaas...' });
try {
const res = await fetch('/api/admin/test-asaas', {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await res.json();
setAsaasTestResult({
loading: false,
success: data.success,
message: data.message || (data.success ? 'Conexão estabelecida com sucesso!' : 'Falha na conexão.')
});
} catch (err: any) {
setAsaasTestResult({
loading: false,
success: false,
message: `Erro de rede: ${err.message}`
});
}
};
const fetchCourses = async () => {
try {
const res = await fetch('/api/courses', {
@ -826,6 +850,37 @@ export default function AdminDashboard({ token }: AdminDashboardProps) {
/>
</div>
{/* Test Connection Button & Status */}
<div className="pt-2 border-t border-white/10 space-y-3">
<div className="flex items-center justify-between">
<span className="text-xs font-bold text-zinc-300">Status da Conexão Asaas</span>
<button
type="button"
onClick={handleTestAsaasConnection}
disabled={asaasTestResult?.loading}
className="bg-zinc-800 hover:bg-zinc-700 text-white text-xs font-bold py-2 px-3 rounded-lg border border-white/10 transition-all flex items-center space-x-1.5 cursor-pointer disabled:opacity-50"
>
{asaasTestResult?.loading ? (
<RefreshCw className="w-3.5 h-3.5 animate-spin text-amber-400" />
) : (
<LinkIcon className="w-3.5 h-3.5 text-emerald-400" />
)}
<span>{asaasTestResult?.loading ? 'Verificando...' : 'Testar Conexão Asaas'}</span>
</button>
</div>
{asaasTestResult && (
<div className={`p-3 rounded-lg text-xs flex items-center space-x-2 border ${asaasTestResult.success ? 'bg-emerald-950/40 border-emerald-500/30 text-emerald-300' : 'bg-red-950/40 border-red-500/30 text-red-300'}`}>
{asaasTestResult.success ? (
<CheckCircle2 className="w-4 h-4 text-emerald-400 flex-shrink-0" />
) : (
<AlertTriangle className="w-4 h-4 text-red-400 flex-shrink-0" />
)}
<span className="font-semibold">{asaasTestResult.message}</span>
</div>
)}
</div>
</div>
</div>
@ -833,7 +888,7 @@ export default function AdminDashboard({ token }: AdminDashboardProps) {
<button
type="submit"
disabled={isSavingSettings}
className="bg-primary hover:bg-primary-hover text-black font-bold py-3 px-8 rounded-lg transition-colors flex items-center gap-2"
className="glass-btn-primary text-white font-extrabold py-3.5 px-8 rounded-xl shadow-lg hover:scale-105 transition-all flex items-center gap-2 cursor-pointer disabled:opacity-50"
>
{isSavingSettings ? (
<RefreshCw className="animate-spin" size={18} />
@ -847,6 +902,31 @@ export default function AdminDashboard({ token }: AdminDashboardProps) {
</div>
)}
</div>
{/* SAVE SUCCESS MODAL */}
{showSaveSuccessModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm animate-fade-in">
<div className="glass-card bg-[#141414]/95 border border-emerald-500/30 rounded-2xl p-6 md:p-8 max-w-sm w-full text-center space-y-5 shadow-2xl">
<div className="w-14 h-14 bg-emerald-500/20 border border-emerald-500/40 text-emerald-400 rounded-full flex items-center justify-center mx-auto shadow-[0_0_20px_rgba(16,185,129,0.3)]">
<CheckCircle2 size={32} />
</div>
<div className="space-y-2">
<h3 className="font-display font-extrabold text-xl text-white">Configurações Salvas!</h3>
<p className="text-xs text-zinc-400 leading-relaxed">
As configurações globais do sistema e as credenciais do Asaas foram atualizadas com sucesso no banco de dados.
</p>
</div>
<button
onClick={() => setShowSaveSuccessModal(false)}
className="w-full glass-btn-primary text-white text-xs font-bold py-3 px-4 rounded-xl cursor-pointer hover:scale-105 transition-all"
>
Fechar Janela
</button>
</div>
</div>
)}
</div>
);
}

View File

@ -21,6 +21,51 @@ export class AsaasService {
};
}
/**
* Test the connection to Asaas API
*/
public static async testConnection(): Promise<{ success: boolean; message: string; environment: string }> {
const apiKey = this.getApiKey();
const env = loadDb().settings?.asaasEnvironment || 'sandbox';
if (!apiKey) {
return {
success: false,
message: 'Chave de API (API Key) não foi configurada.',
environment: env
};
}
try {
const response = await fetch(`${this.getApiUrl()}/customers?limit=1`, {
method: 'GET',
headers: this.getHeaders()
});
if (response.ok) {
return {
success: true,
message: `Conexão estabelecida com sucesso no ambiente ${env.toUpperCase()}!`,
environment: env
};
} else {
const errData = await response.json().catch(() => ({}));
const desc = errData.errors?.[0]?.description || `HTTP ${response.status}`;
return {
success: false,
message: `Falha na autenticação Asaas: ${desc}`,
environment: env
};
}
} catch (err: any) {
return {
success: false,
message: `Erro de conexão de rede: ${err.message || 'Servidor inalcançável'}`,
environment: env
};
}
}
/**
* Create a customer in Asaas
*/