bugfix: all the toasts unfied inside organizations feature. other features still need a refactor for toasts though.

This commit is contained in:
2026-05-17 13:59:00 +03:30
parent 046257c071
commit 2025a868ea
4 changed files with 206 additions and 72 deletions

View File

@@ -1,6 +1,7 @@
'use client';
'use client';
import { useEffect, useState } from 'react';
import { useToast } from '@/lib/hooks/useToast';
import { Check, Trash2, UserPlus, X } from 'lucide-react';
import { useAuth } from '@/lib/hooks/useAuth';
import { useOrganizationInviteLinkCopy } from '@/lib/hooks/useOrganizationInviteLinkCopy';
@@ -18,6 +19,7 @@ import { Badge, organizationConnectionStatusVariant } from '@/components/ui/comm
import { Input } from '@/components/ui/common/Input';
import { SearchBar } from '@/components/ui/common/SearchBar';
import { Table } from '@/components/ui/common/Table';
import { ToastStack } from '@/components/ui/common/Toast';
import type { ApiError } from '@/types/api';
function formatOrganizationStatusLabel(status: string): string {
@@ -63,8 +65,7 @@ type TableMode = 'existing' | 'search';
export default function OrganizationsPage() {
const { currentOrganization } = useAuth();
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
const toast = useToast();
const [query, setQuery] = useState('');
const [mode, setMode] = useState<TableMode>('existing');
@@ -81,8 +82,6 @@ export default function OrganizationsPage() {
const [historyOpen, setHistoryOpen] = useState(false);
const [historyLoading, setHistoryLoading] = useState(false);
const [historyItems, setHistoryItems] = useState<OrganizationInvitationHistoryItemDto[]>([]);
const [historyCopyError, setHistoryCopyError] = useState('');
const [historyCopySuccess, setHistoryCopySuccess] = useState('');
const {
copiedId,
@@ -99,12 +98,12 @@ export default function OrganizationsPage() {
async function loadList() {
setLoading(true);
setError('');
toast.setError('');
try {
const res = await organizationApi.list();
setItems(res.data.items);
} catch (e) {
setError(formatApiMessage(e));
toast.showError(formatApiMessage(e));
} finally {
setLoading(false);
}
@@ -114,12 +113,6 @@ export default function OrganizationsPage() {
void loadList();
}, []);
useEffect(() => {
if (!success) return;
const t = setTimeout(() => setSuccess(''), 4000);
return () => clearTimeout(t);
}, [success]);
async function runSearch() {
const q = query.trim();
if (!q) {
@@ -130,14 +123,14 @@ export default function OrganizationsPage() {
}
setSearching(true);
setError('');
toast.setError('');
setMode('search');
setShowInviteForm(false);
try {
const res = await organizationApi.search(q);
setSearchResults(res.data);
} catch (e) {
setError(formatApiMessage(e));
toast.showError(formatApiMessage(e));
setSearchResults([]);
} finally {
setSearching(false);
@@ -146,16 +139,16 @@ export default function OrganizationsPage() {
async function submitConnectionRequest(targetOrganizationId: string) {
setPendingConnectionRowId(targetOrganizationId);
setError('');
toast.setError('');
try {
await organizationApi.createConnectionRequest(targetOrganizationId);
setSuccess(`${counterpartLabel} connection request sent.`);
toast.showSuccess(`${counterpartLabel} connection request sent.`);
setSearchResults([]);
setQuery('');
setMode('existing');
await loadList();
} catch (e) {
setError(formatApiMessage(e));
toast.showError(formatApiMessage(e));
} finally {
setPendingConnectionRowId(null);
}
@@ -163,14 +156,14 @@ export default function OrganizationsPage() {
async function sendInvite() {
setInviteLoading(true);
setError('');
toast.setError('');
try {
const res = await organizationApi.invite({
organizationName: manualOrganizationName.trim(),
ownerEmail: manualOwnerEmail.trim(),
});
storeInviteLink(res.data.invitationId, manualOwnerEmail, res.data.invitationUrl);
setSuccess(`Invitation link created for ${manualOwnerEmail.trim()}`);
toast.showSuccess(`Invitation link created for ${manualOwnerEmail.trim()}`);
setManualOrganizationName('');
setManualOwnerEmail('');
setShowInviteForm(false);
@@ -179,7 +172,7 @@ export default function OrganizationsPage() {
setSearchResults([]);
await loadList();
} catch (e) {
setError(formatApiMessage(e));
toast.showError(formatApiMessage(e));
} finally {
setInviteLoading(false);
}
@@ -195,38 +188,34 @@ export default function OrganizationsPage() {
async function openInvitationHistory() {
setHistoryOpen(true);
setHistoryLoading(true);
setHistoryCopyError('');
setHistoryCopySuccess('');
setError('');
toast.clear();
try {
await loadInvitationHistory();
} catch (e) {
setError(formatApiMessage(e));
toast.showError(formatApiMessage(e));
} finally {
setHistoryLoading(false);
}
}
async function handleHistoryCopy(invitation: OrganizationInvitationHistoryItemDto) {
setHistoryCopyError('');
setHistoryCopySuccess('');
toast.setError('');
try {
await copyInvitationLink(invitation, {
onRegenerated: async () => {
await loadInvitationHistory();
},
});
setHistoryCopySuccess('Invitation link copied to clipboard.');
setTimeout(() => setHistoryCopySuccess(''), 3000);
toast.showSuccess('Invitation link copied to clipboard.');
} catch (e) {
setHistoryCopyError(formatApiMessage(e));
toast.showError(formatApiMessage(e));
}
}
async function handleCopyInvitationFromRow(row: CounterpartItemDto) {
const target = invitationTargetFromConnectionRow(row, currentOrganization!.id);
if (!target) return;
setError('');
toast.setError('');
try {
await copyInvitationLink(
{
@@ -243,24 +232,23 @@ export default function OrganizationsPage() {
},
},
);
setSuccess('Invitation link copied to clipboard.');
setTimeout(() => setSuccess(''), 3000);
toast.showSuccess('Invitation link copied to clipboard.');
} catch (e) {
setError(formatApiMessage(e));
toast.showError(formatApiMessage(e));
}
}
async function respondToPendingConnection(connectionId: string, action: 'ACCEPT' | 'REJECT') {
setPendingConnectionRowId(connectionId);
setError('');
toast.setError('');
try {
await organizationApi.respondToConnectionRequest(connectionId, action);
setSuccess(
toast.showSuccess(
action === 'ACCEPT' ? 'Connection request accepted.' : 'Connection request declined.',
);
await loadList();
} catch (e) {
setError(formatApiMessage(e));
toast.showError(formatApiMessage(e));
} finally {
setPendingConnectionRowId(null);
}
@@ -268,13 +256,13 @@ export default function OrganizationsPage() {
async function deleteConnection(connectionId: string) {
setDeleteConnectionRowId(connectionId);
setError('');
toast.setError('');
try {
await organizationApi.deleteConnection(connectionId);
setSuccess('Connection removed.');
toast.showSuccess('Connection removed.');
await loadList();
} catch (e) {
setError(formatApiMessage(e));
toast.showError(formatApiMessage(e));
} finally {
setDeleteConnectionRowId(null);
}
@@ -306,16 +294,7 @@ export default function OrganizationsPage() {
</Button>
</div>
{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>
)}
{!historyOpen && <ToastStack {...toast.messages} />}
<SearchBar
value={query}
@@ -530,18 +509,13 @@ export default function OrganizationsPage() {
<InvitationHistoryDialog
open={historyOpen}
onClose={() => {
setHistoryOpen(false);
setHistoryCopyError('');
setHistoryCopySuccess('');
}}
onClose={() => setHistoryOpen(false)}
loading={historyLoading}
items={historyItems}
copiedId={copiedId}
copyingInvitationId={copyingInvitationId}
onCopy={(invitation) => void handleHistoryCopy(invitation)}
copyError={historyCopyError}
copySuccess={historyCopySuccess}
toastMessages={toast.messages}
/>
</div>
);