bugfix: staffs feature toasts unified with the other features.

This commit is contained in:
2026-05-18 14:20:50 +03:30
parent 7dba7cc144
commit 81fe14823f

View File

@@ -25,7 +25,9 @@ import { Badge } from '@/components/ui/shared/Badge';
import { Input } from '@/components/ui/shared/Input';
import { Checkbox } from '@/components/ui/shared/Checkbox';
import { Table } from '@/components/ui/shared/Table';
import type { ApiError } from '@/types/api';
import { ToastStack } from '@/components/ui/shared/Toast';
import { formatApiErrorMessage } from '@/components/shared/formatApiError';
import { useToast } from '@/lib/hooks/useToast';
type StoredInviteLink = {
membershipId: string;
@@ -54,14 +56,6 @@ function writeStoredInviteLinks(orgId: string, links: Record<string, StoredInvit
window.localStorage.setItem(inviteLinksStorageKey(orgId), JSON.stringify(links));
}
function formatApiMessage(err: unknown): string {
if (!err || typeof err !== 'object') return 'Something went wrong';
const m = (err as ApiError).message;
if (Array.isArray(m)) return m.join(', ');
if (typeof m === 'string') return m;
return 'Something went wrong';
}
function canShareStaffInviteLink(member: StaffMemberDto): boolean {
return !member.isOwner && member.invitationStatus !== 'ACTIVE';
}
@@ -136,8 +130,7 @@ export default function StaffPage() {
unlimited: boolean;
} | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
const toast = useToast();
const [inviteOpen, setInviteOpen] = useState(false);
const [inviteEmail, setInviteEmail] = useState('');
@@ -169,14 +162,14 @@ export default function StaffPage() {
}, [seats]);
const load = useCallback(async () => {
setError('');
toast.setError('');
setLoading(true);
try {
const res = await staffApi.list();
setMembers(res.data.members);
setSeats(res.data.seats);
} catch (e) {
setError(formatApiMessage(e));
toast.showError(formatApiErrorMessage(e, 'Failed to load staff.'));
} finally {
setLoading(false);
}
@@ -221,17 +214,11 @@ export default function StaffPage() {
}
}, [currentOrganization, router]);
useEffect(() => {
if (!success) return;
const t = setTimeout(() => setSuccess(''), 4000);
return () => clearTimeout(t);
}, [success]);
async function copyStaffInviteLink(member: StaffMemberDto) {
if (!canShareStaffInviteLink(member)) return;
setCopyingInviteMembershipId(member.id);
setError('');
toast.setError('');
try {
let invitationUrl = pendingInviteLinks[member.id]?.invitationUrl;
if (!invitationUrl || member.invitationStatus === 'EXPIRED') {
@@ -257,7 +244,7 @@ export default function StaffPage() {
await load();
}
} catch (e) {
setError(formatApiMessage(e));
toast.showError(formatApiErrorMessage(e, 'Could not copy invitation link.'));
} finally {
setCopyingInviteMembershipId(null);
}
@@ -265,7 +252,7 @@ export default function StaffPage() {
async function submitInvite() {
setInviteLoading(true);
setError('');
toast.setError('');
setLastInviteInfo(null);
const displayName = inviteName.trim();
const displayEmail = inviteEmail.trim();
@@ -295,14 +282,13 @@ export default function StaffPage() {
setPendingInviteLinks(nextLinks);
writeStoredInviteLinks(currentOrganization.id, nextLinks);
}
setSuccess('');
setInviteOpen(false);
setInviteEmail('');
setInviteName('');
setInvitePerms(emptyFeaturePermissionState());
await load();
} catch (e) {
setError(formatApiMessage(e));
toast.showError(formatApiErrorMessage(e, 'Failed to send invitation.'));
} finally {
setInviteLoading(false);
}
@@ -320,17 +306,17 @@ export default function StaffPage() {
async function submitEdit() {
if (!editing) return;
setEditLoading(true);
setError('');
toast.setError('');
try {
await staffApi.updateMember(editing.id, {
name: editName.trim(),
permissionNames: permissionNamesFromFeatureState(editPerms),
});
setSuccess('Member updated');
toast.showSuccess('Member updated.');
setEditing(null);
await load();
} catch (e) {
setError(formatApiMessage(e));
toast.showError(formatApiErrorMessage(e, 'Failed to update member.'));
} finally {
setEditLoading(false);
}
@@ -343,13 +329,13 @@ export default function StaffPage() {
} else {
if (!confirm(`Remove ${m.name} from this organization?`)) return;
}
setError('');
toast.setError('');
try {
await staffApi.removeMember(m.id);
setSuccess('Member removed');
toast.showSuccess('Member removed.');
await load();
} catch (e) {
setError(formatApiMessage(e));
toast.showError(formatApiErrorMessage(e, 'Failed to remove member.'));
}
}
@@ -383,6 +369,8 @@ export default function StaffPage() {
</Button>
</div>
<ToastStack {...toast.messages} />
{seats && (
<p className="text-sm text-text-secondary">
Seats:{' '}
@@ -400,18 +388,6 @@ export default function StaffPage() {
</p>
)}
{error && (
<div className="rounded-[var(--radius-md)] border border-red-500/40 bg-red-500/10 px-4 py-3 text-sm text-red-700 dark:text-red-300">
{error}
</div>
)}
{success && (
<div className="rounded-[var(--radius-md)] border border-primary/30 bg-primary-soft/40 px-4 py-3 text-sm text-text-primary">
{success}
</div>
)}
{lastInviteInfo && (
<div className="relative rounded-[var(--radius-md)] border border-border-strong bg-background-secondary/90 px-4 py-3 pr-12 shadow-[inset_0_1px_0_rgba(255,255,255,0.04)] space-y-3">
<button
@@ -453,7 +429,7 @@ export default function StaffPage() {
}
void (async () => {
setCopyingInviteMembershipId(lastInviteInfo.membershipId);
setError('');
toast.setError('');
try {
const res = await staffApi.getInvitationLink(lastInviteInfo.membershipId);
if (currentOrganization?.id) {
@@ -473,7 +449,7 @@ export default function StaffPage() {
setCopiedInviteMembershipId(lastInviteInfo.membershipId);
setTimeout(() => setCopiedInviteMembershipId(null), 1500);
} catch (e) {
setError(formatApiMessage(e));
toast.showError(formatApiErrorMessage(e, 'Could not copy invitation link.'));
} finally {
setCopyingInviteMembershipId(null);
}