bugfix/demo-bugs-fixed #19
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user