Fix admin seed, open username/email changes, invite refresh & revocation
- Seed checks for any admin role instead of username='admin' - Username change open to all registered users (was admin-only) - New change-email endpoint requiring password confirmation - Settings page: inline editing for username and email - Invitations: await refresh after generate so list updates visibly - Invitations: revoke button to delete unused invites (admin only) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Copy, Check, Plus, RefreshCw } from 'lucide-react';
|
||||
import { Copy, Check, Plus, RefreshCw, Trash2 } from 'lucide-react';
|
||||
import { api } from '@/lib/api';
|
||||
|
||||
interface Invitation {
|
||||
@@ -31,6 +31,7 @@ export function InvitationsPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [generating, setGenerating] = useState(false);
|
||||
const [copiedCode, setCopiedCode] = useState<string | null>(null);
|
||||
const [revoking, setRevoking] = useState<string | null>(null);
|
||||
|
||||
const fetchInvitations = useCallback(async () => {
|
||||
try {
|
||||
@@ -53,7 +54,7 @@ export function InvitationsPage() {
|
||||
await navigator.clipboard.writeText(url);
|
||||
setCopiedCode(result.code);
|
||||
setTimeout(() => setCopiedCode(null), 2000);
|
||||
fetchInvitations();
|
||||
await fetchInvitations();
|
||||
} catch {
|
||||
// silent
|
||||
} finally {
|
||||
@@ -61,6 +62,18 @@ export function InvitationsPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRevoke(id: string) {
|
||||
setRevoking(id);
|
||||
try {
|
||||
await api.invites.revoke(id);
|
||||
await fetchInvitations();
|
||||
} catch {
|
||||
// silent
|
||||
} finally {
|
||||
setRevoking(null);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCopyCode(code: string) {
|
||||
const url = `${window.location.origin}?invite=${code}`;
|
||||
await navigator.clipboard.writeText(url);
|
||||
@@ -132,13 +145,23 @@ export function InvitationsPage() {
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
{!inv.used && (
|
||||
<button
|
||||
onClick={() => handleCopyCode(inv.code)}
|
||||
className="text-surface-400 hover:text-surface-200 transition-colors"
|
||||
title="Copy invite link"
|
||||
>
|
||||
{copiedCode === inv.code ? <Check size={14} className="text-accent" /> : <Copy size={14} />}
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => handleCopyCode(inv.code)}
|
||||
className="text-surface-400 hover:text-surface-200 transition-colors"
|
||||
title="Copy invite link"
|
||||
>
|
||||
{copiedCode === inv.code ? <Check size={14} className="text-accent" /> : <Copy size={14} />}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleRevoke(inv.id)}
|
||||
disabled={revoking === inv.id}
|
||||
className="text-surface-400 hover:text-danger transition-colors disabled:opacity-50"
|
||||
title="Revoke invitation"
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user