Files
b2bcall/apps/frontend/src/app/trocar-senha/page.tsx
B2BCall Bootstrap 6273b32214 feat: add frontend, nginx reverse proxy and monitoring/reports extras
- apps/frontend: Next.js 15 (App Router) + Tailwind v4 + componentes estilo
  shadcn/ui sobre Radix UI + TanStack Query. Tema light/dark, logo
  processada. Menu completo (secao 52) com gating por permissao real.
  Todas as telas do checklist de aceite (secao 90) conectadas a endpoints
  reais (nao mockup): login, usuarios, perfis/permissoes, ramais/troncos,
  dialplan, filas/agentes, console do agente, campanhas (CPS/CSV/
  iniciar/pausar), monitoramento ao vivo (polling, nao WebSocket real),
  TME/TMA, busca/export de chamadas, administracao do Asterisk, auditoria
- infrastructure/nginx: reverse proxy colocando frontend+API na mesma
  origem (porta 80), antecipado da Fase 9 pois a API nao publica porta
  propria
- apps/api: GET /api/monitoring/agents (estado corrente real via
  agent_state_events em aberto) e filtro queueId em GET /api/reports/calls

Pendencia registrada: tela de Callbacks nao implementada (schema existe
desde a Fase 6, mas nunca houve controller/service — construir a tela sem
API real seria mockup). Verificacao visual em navegador nao foi possivel
neste ambiente headless; validado via tsc/eslint/next build limpos + curl
reproduzindo as chamadas do navegador (middleware de auth, 24 paginas
protegidas via Nginx, endpoints de dados com cookie de sessao).
2026-08-27 17:35:34 -03:00

93 lines
3.2 KiB
TypeScript

'use client';
import * as React from 'react';
import Image from 'next/image';
import { Loader2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { authService } from '@/services/auth';
import { errorMessage } from '@/lib/error-message';
export default function ChangePasswordPage() {
const [current, setCurrent] = React.useState('');
const [next, setNext] = React.useState('');
const [confirm, setConfirm] = React.useState('');
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<string | null>(null);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setError(null);
if (next !== confirm) {
setError('As senhas não coincidem.');
return;
}
setLoading(true);
try {
await authService.changePassword(current, next);
window.location.href = '/';
} catch (err) {
setError(errorMessage(err));
} finally {
setLoading(false);
}
}
return (
<div className="flex min-h-screen items-center justify-center bg-secondary/40 px-4">
<Card className="w-full max-w-sm">
<CardHeader className="items-center text-center">
<Image src="/logo.png" alt="B2BCall" width={64} height={64} className="mb-2 rounded-xl" />
<CardTitle className="text-lg">Troca de senha obrigatória</CardTitle>
<CardDescription>
Defina uma nova senha para continuar utilizando o sistema.
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<div className="flex flex-col gap-1.5">
<Label htmlFor="current">Senha atual</Label>
<Input
id="current"
type="password"
required
value={current}
onChange={(e) => setCurrent(e.target.value)}
/>
</div>
<div className="flex flex-col gap-1.5">
<Label htmlFor="next">Nova senha</Label>
<Input
id="next"
type="password"
required
minLength={8}
value={next}
onChange={(e) => setNext(e.target.value)}
/>
</div>
<div className="flex flex-col gap-1.5">
<Label htmlFor="confirm">Confirmar nova senha</Label>
<Input
id="confirm"
type="password"
required
minLength={8}
value={confirm}
onChange={(e) => setConfirm(e.target.value)}
/>
</div>
{error && <p className="text-sm text-destructive">{error}</p>}
<Button type="submit" disabled={loading} className="mt-1">
{loading && <Loader2 className="animate-spin" />}
Salvar nova senha
</Button>
</form>
</CardContent>
</Card>
</div>
);
}