Files
scoreodonto.com/frontend/components/ChangePasswordModal.tsx
T
VPS 4 Builder ba062e92d0 fix(login): render standalone views edge-to-edge e esconde scrollbar do carrossel de onboarding
- App.tsx: wrappers de padding (p-4/md:p-8) e max-w-7xl/mx-auto agora só se aplicam quando não é tela standalone; login/landing/public renderizam full-bleed
- OnboardingChat.tsx: classe ob-noscroll esconde a barra de rolagem do slide intro (mantém scroll), substituindo a custom-scrollbar indefinida no fluxo de login

Inclui também demais alterações pendentes do working tree (snapshot do estado atual de dev).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 13:23:19 +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>
);
};