Auto-expire revoked invites and allow reactivation

This commit is contained in:
Esdras Renan 2025-10-13 15:17:11 -03:00
parent 05f5af5ba6
commit b60f27b2dc
3 changed files with 96 additions and 3 deletions

View file

@ -140,6 +140,13 @@ function extractMachineId(email: string): string | null {
return match ? match[1] : null
}
function canReactivateInvite(invite: AdminInvite): boolean {
if (invite.status !== "revoked" || !invite.revokedAt) return false
const revokedDate = new Date(invite.revokedAt)
const limit = Date.now() - 7 * 24 * 60 * 60 * 1000
return revokedDate.getTime() > limit
}
export function AdminUsersManager({ initialUsers, initialInvites, roleOptions, defaultTenantId }: Props) {
const [users, setUsers] = useState<AdminUser[]>(initialUsers)
const [invites, setInvites] = useState<AdminInvite[]>(initialInvites)
@ -152,6 +159,7 @@ export function AdminUsersManager({ initialUsers, initialInvites, roleOptions, d
const [expiresInDays, setExpiresInDays] = useState("7")
const [lastInviteLink, setLastInviteLink] = useState<string | null>(null)
const [revokingId, setRevokingId] = useState<string | null>(null)
const [reactivatingId, setReactivatingId] = useState<string | null>(null)
const [isPending, startTransition] = useTransition()
const [linkEmail, setLinkEmail] = useState("")
@ -315,6 +323,31 @@ export function AdminUsersManager({ initialUsers, initialInvites, roleOptions, d
}
}
async function handleReactivate(invite: AdminInvite) {
if (!canReactivateInvite(invite)) return
setReactivatingId(invite.id)
try {
const response = await fetch(`/api/admin/invites/${invite.id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "reactivate" }),
})
if (!response.ok) {
const data = await response.json().catch(() => ({}))
throw new Error(data.error ?? "Falha ao reativar convite")
}
const data = (await response.json()) as { invite: AdminInvite }
const normalized = sanitizeInvite(data.invite)
setInvites((previous) => previous.map((item) => (item.id === normalized.id ? normalized : item)))
toast.success("Convite reativado")
} catch (error) {
const message = error instanceof Error ? error.message : "Não foi possível reativar"
toast.error(message)
} finally {
setReactivatingId(null)
}
}
async function handleAssignCompany(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault()
const normalizedEmail = linkEmail.trim().toLowerCase()
@ -784,6 +817,17 @@ export function AdminUsersManager({ initialUsers, initialInvites, roleOptions, d
{revokingId === invite.id ? "Revogando..." : "Revogar"}
</Button>
) : null}
{invite.status === "revoked" && canReactivateInvite(invite) ? (
<Button
variant="outline"
size="sm"
className="border-amber-400 text-amber-600 hover:bg-amber-50"
onClick={() => handleReactivate(invite)}
disabled={reactivatingId === invite.id}
>
{reactivatingId === invite.id ? "Reativando..." : "Reativar"}
</Button>
) : null}
</div>
</td>
</tr>