Files
scoreodonto.com/frontend/components/ChangePasswordModal.tsx
T
VPS 4 Builder f084656712 feat(agenda): auditoria/autoria, faltas, a-reagendar, presença + RBAC por cargo
Agenda (multi-secretária):
- audit_log genérico + autoria (created/updated/canceled_by) + soft-delete + version (lock otimista)
- status novo vocabulário + índice único PARCIAL (libera slot ao cancelar/faltar)
- endpoints: falta, remarcar, restaurar, historico, pendencias (a reagendar), faltas, familia
- presença: heartbeat (DB última visita + Dragonfly ao vivo, janela 45s)
- frontend: StackModal, AgendaDetailModal, AgendaPresence; lista a-reagendar; pacienteId no fluxo de criação; chips de família + badge de faltas no modal

RBAC por cargo: funcoes.permissoes/nivel; gate de menu por cargo (array vazio herda role); hierarquia só-gerencia-abaixo no backend

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 16:10:58 +02:00

61 lines
3.5 KiB
TypeScript

import React, { useState } from 'react';
import { KeyRound, Loader2, X } from 'lucide-react';
import { HybridBackend } from '../services/backend.ts';
import { useToast } from '../contexts/ToastContext.tsx';
interface Props {
forced?: boolean; // troca obrigatória (1º login com senha temporária) — não pede senha atual nem deixa cancelar
onClose?: () => void;
onDone: () => void;
}
const inp = 'w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-medium text-gray-800 outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-100 transition-all';
export const ChangePasswordModal: React.FC<Props> = ({ forced, onClose, onDone }) => {
const toast = useToast();
const [atual, setAtual] = useState('');
const [nova, setNova] = useState('');
const [conf, setConf] = useState('');
const [saving, setSaving] = useState(false);
const submit = async () => {
if (nova.length < 6) { toast.error('A NOVA SENHA DEVE TER NO MÍNIMO 6 CARACTERES.'); return; }
if (nova !== conf) { toast.error('AS SENHAS NÃO COINCIDEM.'); return; }
setSaving(true);
try {
const r = await HybridBackend.changeMyPassword(forced ? undefined : atual, nova);
if (!r.success) { toast.error(r.message || 'ERRO AO TROCAR SENHA.'); return; }
toast.success('SENHA ALTERADA COM SUCESSO!');
onDone();
} catch { toast.error('ERRO AO TROCAR SENHA.'); }
finally { setSaving(false); }
};
return (
<div className="fixed inset-0 bg-black/60 z-[85] flex items-center justify-center p-4 backdrop-blur-sm">
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-sm">
<div className="px-6 py-4 border-b border-gray-100 flex items-center justify-between">
<h3 className="font-black text-gray-800 uppercase text-sm flex items-center gap-2"><KeyRound size={16} className="text-blue-600" /> {forced ? 'Defina uma nova senha' : 'Trocar senha'}</h3>
{!forced && onClose && <button onClick={onClose} className="p-1.5 hover:bg-gray-100 rounded-lg"><X size={18} /></button>}
</div>
<div className="p-6 space-y-3">
{forced && (
<p className="text-[11px] text-amber-700 bg-amber-50 border border-amber-100 rounded-xl p-3 font-bold uppercase leading-relaxed">
Sua senha é temporária. Defina uma nova para continuar usando o sistema.
</p>
)}
{!forced && <input type="password" placeholder="SENHA ATUAL" value={atual} onChange={e => setAtual(e.target.value)} className={inp} autoComplete="current-password" />}
<input type="password" placeholder="NOVA SENHA" value={nova} onChange={e => setNova(e.target.value)} className={inp} autoComplete="new-password" />
<input type="password" placeholder="CONFIRMAR NOVA SENHA" value={conf} onChange={e => setConf(e.target.value)} className={inp} autoComplete="new-password" />
<div className="flex gap-2 pt-1">
{!forced && onClose && <button onClick={onClose} className="px-4 py-2.5 border border-gray-200 rounded-xl text-[11px] font-black uppercase hover:bg-gray-50">Cancelar</button>}
<button onClick={submit} disabled={saving} className="flex-1 py-2.5 bg-blue-600 hover:bg-blue-700 text-white rounded-xl text-[11px] font-black uppercase disabled:opacity-50 flex items-center justify-center gap-2">
{saving ? <Loader2 size={13} className="animate-spin" /> : <KeyRound size={13} />} Salvar
</button>
</div>
</div>
</div>
</div>
);
};