bugfix/demo-bugs-fixed #18
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useToast } from '@/lib/hooks/useToast';
|
||||||
import { Check, Trash2, UserPlus, X } from 'lucide-react';
|
import { Check, Trash2, UserPlus, X } from 'lucide-react';
|
||||||
import { useAuth } from '@/lib/hooks/useAuth';
|
import { useAuth } from '@/lib/hooks/useAuth';
|
||||||
import { useOrganizationInviteLinkCopy } from '@/lib/hooks/useOrganizationInviteLinkCopy';
|
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 { Input } from '@/components/ui/common/Input';
|
||||||
import { SearchBar } from '@/components/ui/common/SearchBar';
|
import { SearchBar } from '@/components/ui/common/SearchBar';
|
||||||
import { Table } from '@/components/ui/common/Table';
|
import { Table } from '@/components/ui/common/Table';
|
||||||
|
import { ToastStack } from '@/components/ui/common/Toast';
|
||||||
import type { ApiError } from '@/types/api';
|
import type { ApiError } from '@/types/api';
|
||||||
|
|
||||||
function formatOrganizationStatusLabel(status: string): string {
|
function formatOrganizationStatusLabel(status: string): string {
|
||||||
@@ -63,8 +65,7 @@ type TableMode = 'existing' | 'search';
|
|||||||
export default function OrganizationsPage() {
|
export default function OrganizationsPage() {
|
||||||
const { currentOrganization } = useAuth();
|
const { currentOrganization } = useAuth();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState('');
|
const toast = useToast();
|
||||||
const [success, setSuccess] = useState('');
|
|
||||||
|
|
||||||
const [query, setQuery] = useState('');
|
const [query, setQuery] = useState('');
|
||||||
const [mode, setMode] = useState<TableMode>('existing');
|
const [mode, setMode] = useState<TableMode>('existing');
|
||||||
@@ -81,8 +82,6 @@ export default function OrganizationsPage() {
|
|||||||
const [historyOpen, setHistoryOpen] = useState(false);
|
const [historyOpen, setHistoryOpen] = useState(false);
|
||||||
const [historyLoading, setHistoryLoading] = useState(false);
|
const [historyLoading, setHistoryLoading] = useState(false);
|
||||||
const [historyItems, setHistoryItems] = useState<OrganizationInvitationHistoryItemDto[]>([]);
|
const [historyItems, setHistoryItems] = useState<OrganizationInvitationHistoryItemDto[]>([]);
|
||||||
const [historyCopyError, setHistoryCopyError] = useState('');
|
|
||||||
const [historyCopySuccess, setHistoryCopySuccess] = useState('');
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
copiedId,
|
copiedId,
|
||||||
@@ -99,12 +98,12 @@ export default function OrganizationsPage() {
|
|||||||
|
|
||||||
async function loadList() {
|
async function loadList() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError('');
|
toast.setError('');
|
||||||
try {
|
try {
|
||||||
const res = await organizationApi.list();
|
const res = await organizationApi.list();
|
||||||
setItems(res.data.items);
|
setItems(res.data.items);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(formatApiMessage(e));
|
toast.showError(formatApiMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -114,12 +113,6 @@ export default function OrganizationsPage() {
|
|||||||
void loadList();
|
void loadList();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!success) return;
|
|
||||||
const t = setTimeout(() => setSuccess(''), 4000);
|
|
||||||
return () => clearTimeout(t);
|
|
||||||
}, [success]);
|
|
||||||
|
|
||||||
async function runSearch() {
|
async function runSearch() {
|
||||||
const q = query.trim();
|
const q = query.trim();
|
||||||
if (!q) {
|
if (!q) {
|
||||||
@@ -130,14 +123,14 @@ export default function OrganizationsPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setSearching(true);
|
setSearching(true);
|
||||||
setError('');
|
toast.setError('');
|
||||||
setMode('search');
|
setMode('search');
|
||||||
setShowInviteForm(false);
|
setShowInviteForm(false);
|
||||||
try {
|
try {
|
||||||
const res = await organizationApi.search(q);
|
const res = await organizationApi.search(q);
|
||||||
setSearchResults(res.data);
|
setSearchResults(res.data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(formatApiMessage(e));
|
toast.showError(formatApiMessage(e));
|
||||||
setSearchResults([]);
|
setSearchResults([]);
|
||||||
} finally {
|
} finally {
|
||||||
setSearching(false);
|
setSearching(false);
|
||||||
@@ -146,16 +139,16 @@ export default function OrganizationsPage() {
|
|||||||
|
|
||||||
async function submitConnectionRequest(targetOrganizationId: string) {
|
async function submitConnectionRequest(targetOrganizationId: string) {
|
||||||
setPendingConnectionRowId(targetOrganizationId);
|
setPendingConnectionRowId(targetOrganizationId);
|
||||||
setError('');
|
toast.setError('');
|
||||||
try {
|
try {
|
||||||
await organizationApi.createConnectionRequest(targetOrganizationId);
|
await organizationApi.createConnectionRequest(targetOrganizationId);
|
||||||
setSuccess(`${counterpartLabel} connection request sent.`);
|
toast.showSuccess(`${counterpartLabel} connection request sent.`);
|
||||||
setSearchResults([]);
|
setSearchResults([]);
|
||||||
setQuery('');
|
setQuery('');
|
||||||
setMode('existing');
|
setMode('existing');
|
||||||
await loadList();
|
await loadList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(formatApiMessage(e));
|
toast.showError(formatApiMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setPendingConnectionRowId(null);
|
setPendingConnectionRowId(null);
|
||||||
}
|
}
|
||||||
@@ -163,14 +156,14 @@ export default function OrganizationsPage() {
|
|||||||
|
|
||||||
async function sendInvite() {
|
async function sendInvite() {
|
||||||
setInviteLoading(true);
|
setInviteLoading(true);
|
||||||
setError('');
|
toast.setError('');
|
||||||
try {
|
try {
|
||||||
const res = await organizationApi.invite({
|
const res = await organizationApi.invite({
|
||||||
organizationName: manualOrganizationName.trim(),
|
organizationName: manualOrganizationName.trim(),
|
||||||
ownerEmail: manualOwnerEmail.trim(),
|
ownerEmail: manualOwnerEmail.trim(),
|
||||||
});
|
});
|
||||||
storeInviteLink(res.data.invitationId, manualOwnerEmail, res.data.invitationUrl);
|
storeInviteLink(res.data.invitationId, manualOwnerEmail, res.data.invitationUrl);
|
||||||
setSuccess(`Invitation link created for ${manualOwnerEmail.trim()}`);
|
toast.showSuccess(`Invitation link created for ${manualOwnerEmail.trim()}`);
|
||||||
setManualOrganizationName('');
|
setManualOrganizationName('');
|
||||||
setManualOwnerEmail('');
|
setManualOwnerEmail('');
|
||||||
setShowInviteForm(false);
|
setShowInviteForm(false);
|
||||||
@@ -179,7 +172,7 @@ export default function OrganizationsPage() {
|
|||||||
setSearchResults([]);
|
setSearchResults([]);
|
||||||
await loadList();
|
await loadList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(formatApiMessage(e));
|
toast.showError(formatApiMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setInviteLoading(false);
|
setInviteLoading(false);
|
||||||
}
|
}
|
||||||
@@ -195,38 +188,34 @@ export default function OrganizationsPage() {
|
|||||||
async function openInvitationHistory() {
|
async function openInvitationHistory() {
|
||||||
setHistoryOpen(true);
|
setHistoryOpen(true);
|
||||||
setHistoryLoading(true);
|
setHistoryLoading(true);
|
||||||
setHistoryCopyError('');
|
toast.clear();
|
||||||
setHistoryCopySuccess('');
|
|
||||||
setError('');
|
|
||||||
try {
|
try {
|
||||||
await loadInvitationHistory();
|
await loadInvitationHistory();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(formatApiMessage(e));
|
toast.showError(formatApiMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setHistoryLoading(false);
|
setHistoryLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleHistoryCopy(invitation: OrganizationInvitationHistoryItemDto) {
|
async function handleHistoryCopy(invitation: OrganizationInvitationHistoryItemDto) {
|
||||||
setHistoryCopyError('');
|
toast.setError('');
|
||||||
setHistoryCopySuccess('');
|
|
||||||
try {
|
try {
|
||||||
await copyInvitationLink(invitation, {
|
await copyInvitationLink(invitation, {
|
||||||
onRegenerated: async () => {
|
onRegenerated: async () => {
|
||||||
await loadInvitationHistory();
|
await loadInvitationHistory();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
setHistoryCopySuccess('Invitation link copied to clipboard.');
|
toast.showSuccess('Invitation link copied to clipboard.');
|
||||||
setTimeout(() => setHistoryCopySuccess(''), 3000);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setHistoryCopyError(formatApiMessage(e));
|
toast.showError(formatApiMessage(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCopyInvitationFromRow(row: CounterpartItemDto) {
|
async function handleCopyInvitationFromRow(row: CounterpartItemDto) {
|
||||||
const target = invitationTargetFromConnectionRow(row, currentOrganization!.id);
|
const target = invitationTargetFromConnectionRow(row, currentOrganization!.id);
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
setError('');
|
toast.setError('');
|
||||||
try {
|
try {
|
||||||
await copyInvitationLink(
|
await copyInvitationLink(
|
||||||
{
|
{
|
||||||
@@ -243,24 +232,23 @@ export default function OrganizationsPage() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
setSuccess('Invitation link copied to clipboard.');
|
toast.showSuccess('Invitation link copied to clipboard.');
|
||||||
setTimeout(() => setSuccess(''), 3000);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(formatApiMessage(e));
|
toast.showError(formatApiMessage(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function respondToPendingConnection(connectionId: string, action: 'ACCEPT' | 'REJECT') {
|
async function respondToPendingConnection(connectionId: string, action: 'ACCEPT' | 'REJECT') {
|
||||||
setPendingConnectionRowId(connectionId);
|
setPendingConnectionRowId(connectionId);
|
||||||
setError('');
|
toast.setError('');
|
||||||
try {
|
try {
|
||||||
await organizationApi.respondToConnectionRequest(connectionId, action);
|
await organizationApi.respondToConnectionRequest(connectionId, action);
|
||||||
setSuccess(
|
toast.showSuccess(
|
||||||
action === 'ACCEPT' ? 'Connection request accepted.' : 'Connection request declined.',
|
action === 'ACCEPT' ? 'Connection request accepted.' : 'Connection request declined.',
|
||||||
);
|
);
|
||||||
await loadList();
|
await loadList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(formatApiMessage(e));
|
toast.showError(formatApiMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setPendingConnectionRowId(null);
|
setPendingConnectionRowId(null);
|
||||||
}
|
}
|
||||||
@@ -268,13 +256,13 @@ export default function OrganizationsPage() {
|
|||||||
|
|
||||||
async function deleteConnection(connectionId: string) {
|
async function deleteConnection(connectionId: string) {
|
||||||
setDeleteConnectionRowId(connectionId);
|
setDeleteConnectionRowId(connectionId);
|
||||||
setError('');
|
toast.setError('');
|
||||||
try {
|
try {
|
||||||
await organizationApi.deleteConnection(connectionId);
|
await organizationApi.deleteConnection(connectionId);
|
||||||
setSuccess('Connection removed.');
|
toast.showSuccess('Connection removed.');
|
||||||
await loadList();
|
await loadList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(formatApiMessage(e));
|
toast.showError(formatApiMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setDeleteConnectionRowId(null);
|
setDeleteConnectionRowId(null);
|
||||||
}
|
}
|
||||||
@@ -306,16 +294,7 @@ export default function OrganizationsPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{!historyOpen && <ToastStack {...toast.messages} />}
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<SearchBar
|
<SearchBar
|
||||||
value={query}
|
value={query}
|
||||||
@@ -530,18 +509,13 @@ export default function OrganizationsPage() {
|
|||||||
|
|
||||||
<InvitationHistoryDialog
|
<InvitationHistoryDialog
|
||||||
open={historyOpen}
|
open={historyOpen}
|
||||||
onClose={() => {
|
onClose={() => setHistoryOpen(false)}
|
||||||
setHistoryOpen(false);
|
|
||||||
setHistoryCopyError('');
|
|
||||||
setHistoryCopySuccess('');
|
|
||||||
}}
|
|
||||||
loading={historyLoading}
|
loading={historyLoading}
|
||||||
items={historyItems}
|
items={historyItems}
|
||||||
copiedId={copiedId}
|
copiedId={copiedId}
|
||||||
copyingInvitationId={copyingInvitationId}
|
copyingInvitationId={copyingInvitationId}
|
||||||
onCopy={(invitation) => void handleHistoryCopy(invitation)}
|
onCopy={(invitation) => void handleHistoryCopy(invitation)}
|
||||||
copyError={historyCopyError}
|
toastMessages={toast.messages}
|
||||||
copySuccess={historyCopySuccess}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,3 +24,70 @@ export function Toast({ children, variant = 'default', className = '' }: ToastPr
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ToastMessages = {
|
||||||
|
error?: string;
|
||||||
|
success?: string;
|
||||||
|
info?: string;
|
||||||
|
default?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ToastStackProps = ToastMessages & {
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function hasToastMessages(messages: ToastMessages): boolean {
|
||||||
|
return Boolean(messages.error || messages.success || messages.info || messages.default);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Renders active toast messages with shared badge colors (success / warning / danger / default). */
|
||||||
|
export function ToastStack({ error, success, info, default: defaultMessage, className = '' }: ToastStackProps) {
|
||||||
|
if (!hasToastMessages({ error, success, info, default: defaultMessage })) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`space-y-2 ${className}`.trim()} aria-live="polite">
|
||||||
|
{error && <Toast variant="danger">{error}</Toast>}
|
||||||
|
{info && <Toast variant="warning">{info}</Toast>}
|
||||||
|
{success && <Toast variant="success">{success}</Toast>}
|
||||||
|
{defaultMessage && <Toast variant="default">{defaultMessage}</Toast>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ToastViewportPosition = 'inline' | 'top' | 'bottom';
|
||||||
|
|
||||||
|
export type ToastViewportProps = ToastStackProps & {
|
||||||
|
position?: ToastViewportPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
const viewportPositionClass: Record<Exclude<ToastViewportPosition, 'inline'>, string> = {
|
||||||
|
top: 'fixed top-4 left-0 right-0 z-[70] px-4 pointer-events-none',
|
||||||
|
bottom: 'fixed bottom-4 left-0 right-0 z-[70] px-4 pointer-events-none',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positions a ToastStack on the page. Use `inline` below a heading; `bottom` / `top` for overlays.
|
||||||
|
*/
|
||||||
|
export function ToastViewport({
|
||||||
|
position = 'inline',
|
||||||
|
className = '',
|
||||||
|
...messages
|
||||||
|
}: ToastViewportProps) {
|
||||||
|
if (!hasToastMessages(messages)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stack = <ToastStack {...messages} className={className} />;
|
||||||
|
|
||||||
|
if (position === 'inline') {
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={viewportPositionClass[position]}>
|
||||||
|
<div className="pointer-events-auto w-full">{stack}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { DialogCloseButton } from '@/components/ui/common/DialogCloseButton';
|
import { DialogCloseButton } from '@/components/ui/common/DialogCloseButton';
|
||||||
|
import { ToastStack, type ToastMessages } from '@/components/ui/common/Toast';
|
||||||
import type { OrganizationInvitationHistoryItemDto } from '@/lib/api/organization';
|
import type { OrganizationInvitationHistoryItemDto } from '@/lib/api/organization';
|
||||||
import { Badge, organizationConnectionStatusVariant } from '@/components/ui/common/Badge';
|
import { Badge, organizationConnectionStatusVariant } from '@/components/ui/common/Badge';
|
||||||
import { Table } from '@/components/ui/common/Table';
|
import { Table } from '@/components/ui/common/Table';
|
||||||
@@ -28,8 +29,8 @@ type InvitationHistoryDialogProps = {
|
|||||||
copiedId: string | null;
|
copiedId: string | null;
|
||||||
copyingInvitationId: string | null;
|
copyingInvitationId: string | null;
|
||||||
onCopy: (invitation: OrganizationInvitationHistoryItemDto) => void;
|
onCopy: (invitation: OrganizationInvitationHistoryItemDto) => void;
|
||||||
copyError?: string;
|
/** Same page-level toasts, rendered at top of dialog while it is open. */
|
||||||
copySuccess?: string;
|
toastMessages?: ToastMessages;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function InvitationHistoryDialog({
|
export function InvitationHistoryDialog({
|
||||||
@@ -40,8 +41,7 @@ export function InvitationHistoryDialog({
|
|||||||
copiedId,
|
copiedId,
|
||||||
copyingInvitationId,
|
copyingInvitationId,
|
||||||
onCopy,
|
onCopy,
|
||||||
copyError,
|
toastMessages,
|
||||||
copySuccess,
|
|
||||||
}: InvitationHistoryDialogProps) {
|
}: InvitationHistoryDialogProps) {
|
||||||
if (!open) return null;
|
if (!open) return null;
|
||||||
|
|
||||||
@@ -60,17 +60,7 @@ export function InvitationHistoryDialog({
|
|||||||
<DialogCloseButton onClick={onClose} />
|
<DialogCloseButton onClick={onClose} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{copyError && (
|
{toastMessages && <ToastStack {...toastMessages} />}
|
||||||
<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">
|
|
||||||
{copyError}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{copySuccess && (
|
|
||||||
<div className="rounded-[var(--radius-md)] border border-primary/30 bg-primary-soft/40 px-4 py-3 text-sm text-text-primary">
|
|
||||||
{copySuccess}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<p className="text-sm text-text-secondary">Loading invitation history...</p>
|
<p className="text-sm text-text-secondary">Loading invitation history...</p>
|
||||||
|
|||||||
103
frontend/src/lib/hooks/useToast.ts
Normal file
103
frontend/src/lib/hooks/useToast.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import type { ToastMessages } from '@/components/ui/common/Toast';
|
||||||
|
|
||||||
|
const DEFAULT_DURATION_MS = 4000;
|
||||||
|
|
||||||
|
export type UseToastOptions = {
|
||||||
|
successMs?: number;
|
||||||
|
errorMs?: number;
|
||||||
|
infoMs?: number;
|
||||||
|
defaultMs?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useToast(options: UseToastOptions = {}) {
|
||||||
|
const successMs = options.successMs ?? DEFAULT_DURATION_MS;
|
||||||
|
const errorMs = options.errorMs ?? DEFAULT_DURATION_MS;
|
||||||
|
const infoMs = options.infoMs ?? DEFAULT_DURATION_MS;
|
||||||
|
const defaultMs = options.defaultMs ?? DEFAULT_DURATION_MS;
|
||||||
|
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
const [success, setSuccess] = useState('');
|
||||||
|
const [info, setInfo] = useState('');
|
||||||
|
const [defaultMessage, setDefaultMessage] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!success) return;
|
||||||
|
const id = setTimeout(() => setSuccess(''), successMs);
|
||||||
|
return () => clearTimeout(id);
|
||||||
|
}, [success, successMs]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!error) return;
|
||||||
|
const id = setTimeout(() => setError(''), errorMs);
|
||||||
|
return () => clearTimeout(id);
|
||||||
|
}, [error, errorMs]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!info) return;
|
||||||
|
const id = setTimeout(() => setInfo(''), infoMs);
|
||||||
|
return () => clearTimeout(id);
|
||||||
|
}, [info, infoMs]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!defaultMessage) return;
|
||||||
|
const id = setTimeout(() => setDefaultMessage(''), defaultMs);
|
||||||
|
return () => clearTimeout(id);
|
||||||
|
}, [defaultMessage, defaultMs]);
|
||||||
|
|
||||||
|
const clear = useCallback(() => {
|
||||||
|
setError('');
|
||||||
|
setSuccess('');
|
||||||
|
setInfo('');
|
||||||
|
setDefaultMessage('');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const showError = useCallback((message: string) => {
|
||||||
|
setSuccess('');
|
||||||
|
setInfo('');
|
||||||
|
setDefaultMessage('');
|
||||||
|
setError(message);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const showSuccess = useCallback((message: string) => {
|
||||||
|
setError('');
|
||||||
|
setInfo('');
|
||||||
|
setDefaultMessage('');
|
||||||
|
setSuccess(message);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const showInfo = useCallback((message: string) => {
|
||||||
|
setError('');
|
||||||
|
setSuccess('');
|
||||||
|
setDefaultMessage('');
|
||||||
|
setInfo(message);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const showDefault = useCallback((message: string) => {
|
||||||
|
setError('');
|
||||||
|
setSuccess('');
|
||||||
|
setInfo('');
|
||||||
|
setDefaultMessage(message);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const messages: ToastMessages = { error, success, info, default: defaultMessage };
|
||||||
|
|
||||||
|
return {
|
||||||
|
error,
|
||||||
|
success,
|
||||||
|
info,
|
||||||
|
defaultMessage,
|
||||||
|
setError,
|
||||||
|
setSuccess,
|
||||||
|
setInfo,
|
||||||
|
setDefaultMessage,
|
||||||
|
showError,
|
||||||
|
showSuccess,
|
||||||
|
showInfo,
|
||||||
|
showDefault,
|
||||||
|
clear,
|
||||||
|
messages,
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user