feat: add resend capability for platform and account invite codes
Revoke-and-recreate flow for both invite systems with email delivery via Resend API. Includes account invite email template and audit logging. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Building2, Users, Mail, Crown, Loader2, AlertCircle, Check, X, Settings, FolderTree } from 'lucide-react'
|
||||
import { Building2, Users, Mail, Crown, Loader2, AlertCircle, Check, X, Settings, FolderTree, RefreshCw } from 'lucide-react'
|
||||
import { accountsApi } from '@/api/accounts'
|
||||
import type { Account, AccountMember, AccountInvite } from '@/types'
|
||||
import { cn } from '@/lib/utils'
|
||||
@@ -102,6 +102,22 @@ export function AccountSettingsPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const [resendingId, setResendingId] = useState<string | null>(null)
|
||||
|
||||
const handleResendInvite = async (inviteId: string) => {
|
||||
setResendingId(inviteId)
|
||||
try {
|
||||
await accountsApi.resendInvite(inviteId)
|
||||
toast.success('Invite resent with a new code')
|
||||
const invitesData = await accountsApi.getInvites()
|
||||
setInvites(invitesData)
|
||||
} catch {
|
||||
toast.error('Failed to resend invite')
|
||||
} finally {
|
||||
setResendingId(null)
|
||||
}
|
||||
}
|
||||
|
||||
const handleRemoveMember = async (userId: string) => {
|
||||
try {
|
||||
await accountsApi.removeMember(userId)
|
||||
@@ -433,12 +449,28 @@ export function AccountSettingsPage() {
|
||||
<div>
|
||||
<p className="text-sm text-white">{invite.email}</p>
|
||||
<p className="text-xs text-white/40">
|
||||
Expires {new Date(invite.expires_at).toLocaleDateString()}
|
||||
{invite.expires_at
|
||||
? `Expires ${new Date(invite.expires_at).toLocaleDateString()}`
|
||||
: 'No expiration'}
|
||||
</p>
|
||||
</div>
|
||||
<span className="rounded-full bg-white/10 px-2.5 py-0.5 text-xs text-white/70">
|
||||
{invite.role}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="rounded-full bg-white/10 px-2.5 py-0.5 text-xs text-white/70">
|
||||
{invite.role}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => handleResendInvite(invite.id)}
|
||||
disabled={resendingId === invite.id}
|
||||
className="text-white/40 hover:text-white disabled:opacity-50"
|
||||
title="Resend invite"
|
||||
>
|
||||
{resendingId === invite.id ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user