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
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 43s
Details
This commit is contained in:
parent
bd9403a872
commit
c4af3c053b
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue