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 });
|
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
|
// Admin manually unlock or lock a course for a student
|
||||||
app.post('/api/admin/students/:id/unlock-course', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => {
|
app.post('/api/admin/students/:id/unlock-course', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => {
|
||||||
const { courseId, unlocked } = req.body;
|
const { courseId, unlocked } = req.body;
|
||||||
|
|
|
||||||
|
|
@ -392,6 +392,21 @@ export default function AdminContentManager({ token }: AdminContentManagerProps)
|
||||||
)}
|
)}
|
||||||
</div>
|
</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 --- */}
|
{/* --- RENDER COURSE FORM --- */}
|
||||||
{showCourseForm && (
|
{showCourseForm && (
|
||||||
<div className="glass-card p-6 rounded-2xl space-y-6">
|
<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>
|
<span>Nova Aula</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Manage Activity */}
|
{/* Manage Activity / Questions */}
|
||||||
<button
|
<button
|
||||||
onClick={() => handleManageActivity(mod.id)}
|
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" />
|
<CheckCircle className="w-4 h-4 text-white" />
|
||||||
<span>Gerenciar Atividade</span>
|
<span>📝 Criar / Editar Questões</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Edit Module */}
|
{/* 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">
|
<div className="p-5 border-t border-white/5 bg-white/3 flex items-center justify-between">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleSelectCourse(course)}
|
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" />
|
<ChevronRight className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,8 @@ export default function AdminDashboard({ token }: AdminDashboardProps) {
|
||||||
// Settings State
|
// Settings State
|
||||||
const [settings, setSettings] = useState<any>(null);
|
const [settings, setSettings] = useState<any>(null);
|
||||||
const [isSavingSettings, setIsSavingSettings] = useState(false);
|
const [isSavingSettings, setIsSavingSettings] = useState(false);
|
||||||
|
const [showSaveSuccessModal, setShowSaveSuccessModal] = useState(false);
|
||||||
|
const [asaasTestResult, setAsaasTestResult] = useState<{ loading: boolean; success: boolean | null; message: string } | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchDashboardData();
|
fetchDashboardData();
|
||||||
|
|
@ -106,17 +108,39 @@ export default function AdminDashboard({ token }: AdminDashboardProps) {
|
||||||
body: JSON.stringify(settings)
|
body: JSON.stringify(settings)
|
||||||
});
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
alert('Configurações salvas com sucesso!');
|
setShowSaveSuccessModal(true);
|
||||||
} else {
|
} else {
|
||||||
alert('Erro ao salvar configurações.');
|
alert('Erro ao salvar configurações.');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error saving settings:', err);
|
console.error('Error saving settings:', err);
|
||||||
|
alert('Erro de conexão ao salvar configurações.');
|
||||||
} finally {
|
} finally {
|
||||||
setIsSavingSettings(false);
|
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 () => {
|
const fetchCourses = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/courses', {
|
const res = await fetch('/api/courses', {
|
||||||
|
|
@ -826,6 +850,37 @@ export default function AdminDashboard({ token }: AdminDashboardProps) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -833,7 +888,7 @@ export default function AdminDashboard({ token }: AdminDashboardProps) {
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isSavingSettings}
|
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 ? (
|
{isSavingSettings ? (
|
||||||
<RefreshCw className="animate-spin" size={18} />
|
<RefreshCw className="animate-spin" size={18} />
|
||||||
|
|
@ -847,6 +902,31 @@ export default function AdminDashboard({ token }: AdminDashboardProps) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</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>
|
</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
|
* Create a customer in Asaas
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue