teqo-backup/src/components/campaign/AdvisorPasswordResetButton.tsx
Francisco Ignácio Fontoura Solla 01e52834e2 B19: Gerenciar assessores (/campanha/assessores)
Lista+detalhe única de edição para coordinator/candidate: nome, e-mail e
celular com auto-save debounced (edit-where-you-see-it); carteira de
municípios por chips com agregação de território de identidade,
adição/remoção com UI otimista; criação inline no topo da tabela; ação de
WhatsApp por assessor; reset de senha via e-mail (bloqueado para
placeholders @planilha.invalid). Alinha access de update/phone/atribuição
de município a isCampaignUnrestricted (coordinator + candidate).

Sem migration. Testes int (campaignAdvisorManagement) + unit (schema e
portfólio de municípios) cobrindo access, conflito de e-mail único, cap de
assessores por município e agregação por território/ZE.
2026-07-25 02:40:47 -03:00

82 lines
2.5 KiB
TypeScript

'use client'
import { KeyRoundIcon } from 'lucide-react'
import { useActionState, useEffect } from 'react'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
import { Spinner } from '@/components/ui/Spinner'
import { PLACEHOLDER_RESET_MESSAGE } from '@/lib/schemas/advisor'
import type { CampaignFormActionState } from '@/utilities/campaignFormActionError'
type AdvisorPasswordResetButtonProps = {
advisorId: number
disabled: boolean
/** `icon` is the compact row action; `block` is the labelled button plus hint. */
layout?: 'block' | 'icon'
/** Names the advisor for screen readers in the `icon` layout. */
accessibleName?: string
formAction: (
state: CampaignFormActionState,
formData: FormData,
) => Promise<CampaignFormActionState>
}
export const AdvisorPasswordResetButton = ({
advisorId,
disabled,
layout = 'block',
accessibleName = 'Enviar link de senha',
formAction,
}: AdvisorPasswordResetButtonProps) => {
const [state, submitAction, isPending] = useActionState(formAction, {})
useEffect(() => {
if (state.status === 'success') {
toast.success(state.message ?? 'Link enviado.')
return
}
if (state.message && state.status !== 'success') {
toast.error(state.message)
}
}, [state.message, state.status])
if (layout === 'icon') {
return (
<form action={submitAction}>
<input type="hidden" name="advisorId" value={advisorId} />
<Button
type="submit"
variant="ghost"
size="icon"
className="size-10"
disabled={disabled || isPending}
aria-label={disabled ? `${accessibleName} — ${PLACEHOLDER_RESET_MESSAGE}` : accessibleName}
>
{isPending ? (
<Spinner className="size-4" aria-hidden="true" />
) : (
<KeyRoundIcon className="size-4" aria-hidden="true" />
)}
</Button>
</form>
)
}
return (
<form action={submitAction} className="flex flex-col gap-2">
<input type="hidden" name="advisorId" value={advisorId} />
<Button type="submit" className="min-h-11 w-fit" disabled={disabled || isPending}>
{isPending ? <Spinner data-icon="inline-start" /> : null}
Enviar link de senha
</Button>
{disabled ? (
<p className="text-sm text-muted-foreground">{PLACEHOLDER_RESET_MESSAGE}</p>
) : (
<p className="text-sm text-muted-foreground">
Envia um e-mail com link para o assessor definir a senha de acesso.
</p>
)}
</form>
)
}