bugfix/demo-bugs-fixed #19

Merged
admin merged 10 commits from bugfix/demo-bugs-fixed into master 2026-05-18 19:11:30 +03:30
Showing only changes of commit 81fe14823f - Show all commits

View File

@@ -25,7 +25,9 @@ import { Badge } from '@/components/ui/shared/Badge';
import { Input } from '@/components/ui/shared/Input'; import { Input } from '@/components/ui/shared/Input';
import { Checkbox } from '@/components/ui/shared/Checkbox'; import { Checkbox } from '@/components/ui/shared/Checkbox';
import { Table } from '@/components/ui/shared/Table'; 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 = { type StoredInviteLink = {
membershipId: string; membershipId: string;
@@ -54,14 +56,6 @@ function writeStoredInviteLinks(orgId: string, links: Record<string, StoredInvit
window.localStorage.setItem(inviteLinksStorageKey(orgId), JSON.stringify(links)); 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 { function canShareStaffInviteLink(member: StaffMemberDto): boolean {
return !member.isOwner && member.invitationStatus !== 'ACTIVE'; return !member.isOwner && member.invitationStatus !== 'ACTIVE';
} }
@@ -136,8 +130,7 @@ export default function StaffPage() {
unlimited: boolean; unlimited: boolean;
} | null>(null); } | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(''); const toast = useToast();
const [success, setSuccess] = useState('');
const [inviteOpen, setInviteOpen] = useState(false); const [inviteOpen, setInviteOpen] = useState(false);
const [inviteEmail, setInviteEmail] = useState(''); const [inviteEmail, setInviteEmail] = useState('');
@@ -169,14 +162,14 @@ export default function StaffPage() {
}, [seats]); }, [seats]);
const load = useCallback(async () => { const load = useCallback(async () => {
setError(''); toast.setError('');
setLoading(true); setLoading(true);
try { try {
const res = await staffApi.list(); const res = await staffApi.list();
setMembers(res.data.members); setMembers(res.data.members);
setSeats(res.data.seats); setSeats(res.data.seats);
} catch (e) { } catch (e) {
setError(formatApiMessage(e)); toast.showError(formatApiErrorMessage(e, 'Failed to load staff.'));
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -221,17 +214,11 @@ export default function StaffPage() {
} }
}, [currentOrganization, router]); }, [currentOrganization, router]);
useEffect(() => {
if (!success) return;
const t = setTimeout(() => setSuccess(''), 4000);
return () => clearTimeout(t);
}, [success]);
async function copyStaffInviteLink(member: StaffMemberDto) { async function copyStaffInviteLink(member: StaffMemberDto) {
if (!canShareStaffInviteLink(member)) return; if (!canShareStaffInviteLink(member)) return;
setCopyingInviteMembershipId(member.id); setCopyingInviteMembershipId(member.id);
setError(''); toast.setError('');
try { try {
let invitationUrl = pendingInviteLinks[member.id]?.invitationUrl; let invitationUrl = pendingInviteLinks[member.id]?.invitationUrl;
if (!invitationUrl || member.invitationStatus === 'EXPIRED') { if (!invitationUrl || member.invitationStatus === 'EXPIRED') {
@@ -257,7 +244,7 @@ export default function StaffPage() {
await load(); await load();
} }
} catch (e) { } catch (e) {
setError(formatApiMessage(e)); toast.showError(formatApiErrorMessage(e, 'Could not copy invitation link.'));
} finally { } finally {
setCopyingInviteMembershipId(null); setCopyingInviteMembershipId(null);
} }
@@ -265,7 +252,7 @@ export default function StaffPage() {
async function submitInvite() { async function submitInvite() {
setInviteLoading(true); setInviteLoading(true);
setError(''); toast.setError('');
setLastInviteInfo(null); setLastInviteInfo(null);
const displayName = inviteName.trim(); const displayName = inviteName.trim();
const displayEmail = inviteEmail.trim(); const displayEmail = inviteEmail.trim();
@@ -295,14 +282,13 @@ export default function StaffPage() {
setPendingInviteLinks(nextLinks); setPendingInviteLinks(nextLinks);
writeStoredInviteLinks(currentOrganization.id, nextLinks); writeStoredInviteLinks(currentOrganization.id, nextLinks);
} }
setSuccess('');
setInviteOpen(false); setInviteOpen(false);
setInviteEmail(''); setInviteEmail('');
setInviteName(''); setInviteName('');
setInvitePerms(emptyFeaturePermissionState()); setInvitePerms(emptyFeaturePermissionState());
await load(); await load();
} catch (e) { } catch (e) {
setError(formatApiMessage(e)); toast.showError(formatApiErrorMessage(e, 'Failed to send invitation.'));
} finally { } finally {
setInviteLoading(false); setInviteLoading(false);
} }
@@ -320,17 +306,17 @@ export default function StaffPage() {
async function submitEdit() { async function submitEdit() {
if (!editing) return; if (!editing) return;
setEditLoading(true); setEditLoading(true);
setError(''); toast.setError('');
try { try {
await staffApi.updateMember(editing.id, { await staffApi.updateMember(editing.id, {
name: editName.trim(), name: editName.trim(),
permissionNames: permissionNamesFromFeatureState(editPerms), permissionNames: permissionNamesFromFeatureState(editPerms),
}); });
setSuccess('Member updated'); toast.showSuccess('Member updated.');
setEditing(null); setEditing(null);
await load(); await load();
} catch (e) { } catch (e) {
setError(formatApiMessage(e)); toast.showError(formatApiErrorMessage(e, 'Failed to update member.'));
} finally { } finally {
setEditLoading(false); setEditLoading(false);
} }
@@ -343,13 +329,13 @@ export default function StaffPage() {
} else { } else {
if (!confirm(`Remove ${m.name} from this organization?`)) return; if (!confirm(`Remove ${m.name} from this organization?`)) return;
} }
setError(''); toast.setError('');
try { try {
await staffApi.removeMember(m.id); await staffApi.removeMember(m.id);
setSuccess('Member removed'); toast.showSuccess('Member removed.');
await load(); await load();
} catch (e) { } catch (e) {
setError(formatApiMessage(e)); toast.showError(formatApiErrorMessage(e, 'Failed to remove member.'));
} }
} }
@@ -383,6 +369,8 @@ export default function StaffPage() {
</Button> </Button>
</div> </div>
<ToastStack {...toast.messages} />
{seats && ( {seats && (
<p className="text-sm text-text-secondary"> <p className="text-sm text-text-secondary">
Seats:{' '} Seats:{' '}
@@ -400,18 +388,6 @@ export default function StaffPage() {
</p> </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 && ( {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"> <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 <button
@@ -453,7 +429,7 @@ export default function StaffPage() {
} }
void (async () => { void (async () => {
setCopyingInviteMembershipId(lastInviteInfo.membershipId); setCopyingInviteMembershipId(lastInviteInfo.membershipId);
setError(''); toast.setError('');
try { try {
const res = await staffApi.getInvitationLink(lastInviteInfo.membershipId); const res = await staffApi.getInvitationLink(lastInviteInfo.membershipId);
if (currentOrganization?.id) { if (currentOrganization?.id) {
@@ -473,7 +449,7 @@ export default function StaffPage() {
setCopiedInviteMembershipId(lastInviteInfo.membershipId); setCopiedInviteMembershipId(lastInviteInfo.membershipId);
setTimeout(() => setCopiedInviteMembershipId(null), 1500); setTimeout(() => setCopiedInviteMembershipId(null), 1500);
} catch (e) { } catch (e) {
setError(formatApiMessage(e)); toast.showError(formatApiErrorMessage(e, 'Could not copy invitation link.'));
} finally { } finally {
setCopyingInviteMembershipId(null); setCopyingInviteMembershipId(null);
} }