improvement: updated toasts. no invisible toast anymore.
This commit is contained in:
@@ -40,6 +40,12 @@ Reference: `app/.../treatment/page.tsx` + `components/ui/treatment/TreatmentWork
|
||||
- API clients: `lib/api/`.
|
||||
- Catch blocks: `getUserFacingError(err, tErrors, fallback)` from `components/shared/formatApiError.ts`.
|
||||
|
||||
## Toasts
|
||||
|
||||
- Use `useToast()` for transient page feedback — rendered globally by `ToastProvider` in dashboard layout (fixed bottom, above dialogs).
|
||||
- Do **not** add `<ToastStack />` inside pages or dialogs.
|
||||
- Form/dialog validation: inline error text near the field or submit button, not a toast.
|
||||
|
||||
## When adding UI
|
||||
|
||||
1. Check `components/ui/shared/` for an existing primitive.
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useAuth } from '@/lib/hooks/useAuth';
|
||||
import Sidebar from '@/components/ui/shared/Sidebar';
|
||||
import { TopBarControls } from '@/components/ui/shared/TopBarControls';
|
||||
import { DashboardAccountMenu } from '@/components/ui/dashboard/DashboardAccountMenu';
|
||||
import { ToastProvider } from '@/components/ui/shared/ToastProvider';
|
||||
import {
|
||||
canAccessDashboardRoute,
|
||||
firstAccessibleDashboardPath,
|
||||
@@ -70,7 +71,8 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-[100dvh] app-web-bg text-text-primary">
|
||||
<ToastProvider>
|
||||
<div className="flex h-[100dvh] app-web-bg text-text-primary">
|
||||
{sidebarOpen ? (
|
||||
<button
|
||||
type="button"
|
||||
@@ -94,7 +96,8 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ToastProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import { AppointmentScheduleGrid } from '@/components/ui/appointments/Appointmen
|
||||
import { AppointmentsPatientSearch } from '@/components/ui/appointments/AppointmentsPatientSearch';
|
||||
import { AppointmentScheduleLegend } from '@/components/ui/appointments/AppointmentScheduleLegend';
|
||||
import { ScheduleDayPicker } from '@/components/ui/shared/ScheduleDayPicker';
|
||||
import { ToastStack } from '@/components/ui/shared/Toast';
|
||||
import { useToast } from '@/lib/hooks/useToast';
|
||||
import type { AppointmentPurpose } from '@/types/appointment';
|
||||
import { getUserFacingError } from '@/components/shared/formatApiError';
|
||||
@@ -286,8 +285,6 @@ export function AppointmentsPage() {
|
||||
<p className="text-sm text-text-secondary">{t('subtitle')}</p>
|
||||
</div>
|
||||
|
||||
<ToastStack {...toast.messages} />
|
||||
|
||||
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
|
||||
<div className="xl:col-span-1 space-y-4">
|
||||
<AppointmentsPatientSearch
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { ToastStack } from '@/components/ui/shared/Toast';
|
||||
import { getUserFacingError } from '@/components/shared/formatApiError';
|
||||
import { useAuth } from '@/lib/hooks/useAuth';
|
||||
import { useToast } from '@/lib/hooks/useToast';
|
||||
@@ -466,7 +465,6 @@ export function CasesPage() {
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<ToastStack {...toast.messages} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { MessageSquare } from 'lucide-react';
|
||||
import { ToastStack } from '@/components/ui/shared/Toast';
|
||||
import { Badge } from '@/components/ui/shared/Badge';
|
||||
import { Button } from '@/components/ui/shared/Button';
|
||||
import { FORM_SELECT_CLASS } from '@/components/shared/formSelectStyles';
|
||||
@@ -397,7 +396,6 @@ export function TasksPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ToastStack {...toastMessages} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import { treatmentTypeLabelFromCatalog } from '@/components/shared/treatmentType
|
||||
import { Button } from '@/components/ui/shared/Button';
|
||||
import { MobileDetailBackButton } from '@/components/ui/shared/MobileDetailBackButton';
|
||||
import { SearchBar } from '@/components/ui/shared/SearchBar';
|
||||
import { ToastStack } from '@/components/ui/shared/Toast';
|
||||
import { CaseDetailPanel, CaseTaskProgressBar } from '@/components/ui/lab/CaseDetailPanel';
|
||||
import { LabCaseCommentsPanel } from '@/components/ui/lab/LabCaseCommentsPanel';
|
||||
import {
|
||||
@@ -210,8 +209,6 @@ export function ConnectionCaseHistoryContent({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ToastStack {...toastMessages} />
|
||||
|
||||
<div className="grid gap-4 lg:grid-cols-[minmax(300px,380px)_1fr]">
|
||||
<section
|
||||
className={`rounded-lg border border-border bg-surface p-3 sm:p-4 space-y-3 flex flex-col min-h-0 ${
|
||||
|
||||
@@ -7,12 +7,11 @@ import {
|
||||
ResponsiveDialogOverlay,
|
||||
ResponsiveDialogPanel,
|
||||
} from '@/components/ui/shared/ResponsiveDialog';
|
||||
import { ToastStack, type ToastMessages } from '@/components/ui/shared/Toast';
|
||||
import { Table } from '@/components/ui/shared/Table';
|
||||
import { CopyInvitationLinkButton } from '@/components/ui/organizations/CopyInvitationLinkButton';
|
||||
import type { OrganizationInvitationHistoryItemDto } from '@/lib/api/organization';
|
||||
import { Badge } from '@/components/ui/shared/Badge';
|
||||
import { organizationConnectionStatusVariant } from '@/components/organizations/connectionStatusVariant';
|
||||
import { Table } from '@/components/ui/shared/Table';
|
||||
import { CopyInvitationLinkButton } from '@/components/ui/organizations/CopyInvitationLinkButton';
|
||||
|
||||
function formatTableDate(value: string): string {
|
||||
const d = new Date(value);
|
||||
@@ -28,8 +27,6 @@ type InvitationHistoryDialogProps = {
|
||||
copiedId: string | null;
|
||||
copyingInvitationId: string | null;
|
||||
onCopy: (invitation: OrganizationInvitationHistoryItemDto) => void;
|
||||
/** Same page-level toasts, rendered at top of dialog while it is open. */
|
||||
toastMessages?: ToastMessages;
|
||||
};
|
||||
|
||||
export function InvitationHistoryDialog({
|
||||
@@ -40,7 +37,6 @@ export function InvitationHistoryDialog({
|
||||
copiedId,
|
||||
copyingInvitationId,
|
||||
onCopy,
|
||||
toastMessages,
|
||||
}: InvitationHistoryDialogProps) {
|
||||
const t = useTranslations('organizations');
|
||||
|
||||
@@ -72,8 +68,6 @@ export function InvitationHistoryDialog({
|
||||
<DialogCloseButton onClick={onClose} />
|
||||
</div>
|
||||
|
||||
{toastMessages && <ToastStack {...toastMessages} />}
|
||||
|
||||
{loading ? (
|
||||
<p className="text-sm text-text-secondary">{t('loadingHistory')}</p>
|
||||
) : items.length === 0 ? (
|
||||
|
||||
@@ -24,7 +24,6 @@ import { organizationConnectionStatusVariant } from '@/components/organizations/
|
||||
import { Input } from '@/components/ui/shared/Input';
|
||||
import { SearchBar } from '@/components/ui/shared/SearchBar';
|
||||
import { Table } from '@/components/ui/shared/Table';
|
||||
import { ToastStack } from '@/components/ui/shared/Toast';
|
||||
import { getUserFacingError } from '@/components/shared/formatApiError';
|
||||
|
||||
function formatOrganizationStatusLabel(status: string): string {
|
||||
@@ -327,8 +326,6 @@ export function OrganizationsPage() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{!historyOpen && <ToastStack {...toast.messages} />}
|
||||
|
||||
<SearchBar
|
||||
value={query}
|
||||
onChange={setQuery}
|
||||
@@ -596,7 +593,6 @@ export function OrganizationsPage() {
|
||||
copiedId={copiedId}
|
||||
copyingInvitationId={copyingInvitationId}
|
||||
onCopy={(invitation) => void handleHistoryCopy(invitation)}
|
||||
toastMessages={toast.messages}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Button } from '@/components/ui/shared/Button';
|
||||
import { ToastStack } from '@/components/ui/shared/Toast';
|
||||
import { patientsApi } from '@/lib/api/patients';
|
||||
import { getUserFacingError } from '@/components/shared/formatApiError';
|
||||
import { useAuth } from '@/lib/hooks/useAuth';
|
||||
@@ -125,8 +124,6 @@ export function PatientsPage() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<ToastStack {...toast.messages} />
|
||||
|
||||
{isCreateOpen && (
|
||||
<CreatePatientModal
|
||||
isOpen={isCreateOpen}
|
||||
|
||||
@@ -40,7 +40,10 @@ 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). */
|
||||
/**
|
||||
* Inline toast stack — prefer global `ToastProvider` + `useToast()` for page feedback.
|
||||
* Dialog/form field errors should stay inline near the control.
|
||||
*/
|
||||
export function ToastStack({ error, success, info, default: defaultMessage, className = '' }: ToastStackProps) {
|
||||
if (!hasToastMessages({ error, success, info, default: defaultMessage })) {
|
||||
return null;
|
||||
@@ -63,8 +66,9 @@ export type ToastViewportProps = ToastStackProps & {
|
||||
};
|
||||
|
||||
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',
|
||||
top: 'fixed top-4 left-0 right-0 z-[100] px-4 pointer-events-none',
|
||||
bottom:
|
||||
'fixed bottom-4 left-0 right-0 z-[100] px-4 pb-[max(1rem,env(safe-area-inset-bottom))] pointer-events-none',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -87,7 +91,7 @@ export function ToastViewport({
|
||||
|
||||
return (
|
||||
<div className={viewportPositionClass[position]}>
|
||||
<div className="pointer-events-auto w-full">{stack}</div>
|
||||
<div className="pointer-events-auto mx-auto w-full max-w-md">{stack}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
213
frontend/src/components/ui/shared/ToastProvider.tsx
Normal file
213
frontend/src/components/ui/shared/ToastProvider.tsx
Normal file
@@ -0,0 +1,213 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import {
|
||||
ToastStack,
|
||||
type ToastMessages,
|
||||
type ToastViewportPosition,
|
||||
} from '@/components/ui/shared/Toast';
|
||||
|
||||
const DEFAULT_DURATION_MS = 4000;
|
||||
|
||||
export type UseToastOptions = {
|
||||
successMs?: number;
|
||||
errorMs?: number;
|
||||
infoMs?: number;
|
||||
defaultMs?: number;
|
||||
};
|
||||
|
||||
export type ToastContextValue = {
|
||||
error: string;
|
||||
success: string;
|
||||
info: string;
|
||||
defaultMessage: string;
|
||||
setError: (message: string) => void;
|
||||
setSuccess: (message: string) => void;
|
||||
setInfo: (message: string) => void;
|
||||
setDefaultMessage: (message: string) => void;
|
||||
showError: (message: string) => void;
|
||||
showSuccess: (message: string) => void;
|
||||
showInfo: (message: string) => void;
|
||||
showDefault: (message: string) => void;
|
||||
clear: () => void;
|
||||
messages: ToastMessages;
|
||||
};
|
||||
|
||||
const ToastContext = createContext<ToastContextValue | null>(null);
|
||||
|
||||
function useToastState(options: UseToastOptions = {}): ToastContextValue {
|
||||
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 = useMemo(
|
||||
() => ({ error, success, info, default: defaultMessage }),
|
||||
[error, success, info, defaultMessage],
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
error,
|
||||
success,
|
||||
info,
|
||||
defaultMessage,
|
||||
setError,
|
||||
setSuccess,
|
||||
setInfo,
|
||||
setDefaultMessage,
|
||||
showError,
|
||||
showSuccess,
|
||||
showInfo,
|
||||
showDefault,
|
||||
clear,
|
||||
messages,
|
||||
}),
|
||||
[
|
||||
error,
|
||||
success,
|
||||
info,
|
||||
defaultMessage,
|
||||
showError,
|
||||
showSuccess,
|
||||
showInfo,
|
||||
showDefault,
|
||||
clear,
|
||||
messages,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
type ToastProviderProps = {
|
||||
children: ReactNode;
|
||||
/** Fixed viewport position. Default `bottom` — visible when scrolled and above dialogs. */
|
||||
position?: Exclude<ToastViewportPosition, 'inline'>;
|
||||
};
|
||||
|
||||
function GlobalToastHost({
|
||||
messages,
|
||||
position,
|
||||
}: {
|
||||
messages: ToastMessages;
|
||||
position: Exclude<ToastViewportPosition, 'inline'>;
|
||||
}) {
|
||||
const hasMessage = Boolean(
|
||||
messages.error || messages.success || messages.info || messages.default,
|
||||
);
|
||||
|
||||
if (!hasMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const positionClass =
|
||||
position === 'bottom'
|
||||
? 'fixed bottom-4 left-0 right-0 z-[100] px-4 pb-[max(1rem,env(safe-area-inset-bottom))] pointer-events-none'
|
||||
: 'fixed top-4 left-0 right-0 z-[100] px-4 pt-[max(1rem,env(safe-area-inset-top))] pointer-events-none';
|
||||
|
||||
return createPortal(
|
||||
<div className={positionClass} aria-live="polite">
|
||||
<div className="pointer-events-auto mx-auto w-full max-w-md">
|
||||
<ToastStack {...messages} />
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
);
|
||||
}
|
||||
|
||||
export function ToastProvider({ children, position = 'bottom' }: ToastProviderProps) {
|
||||
const toast = useToastState();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ToastContext.Provider value={toast}>
|
||||
{children}
|
||||
{mounted ? <GlobalToastHost messages={toast.messages} position={position} /> : null}
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
/** Global app toasts — requires `<ToastProvider>` ancestor (dashboard layout). */
|
||||
export function useToast(): ToastContextValue {
|
||||
const context = useContext(ToastContext);
|
||||
if (!context) {
|
||||
throw new Error('useToast must be used within ToastProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@@ -34,7 +34,6 @@ 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 { ToastStack } from '@/components/ui/shared/Toast';
|
||||
import { getUserFacingError } from '@/components/shared/formatApiError';
|
||||
import { StaffMembersMobileList } from '@/components/ui/staff/StaffMembersMobileList';
|
||||
import { useToast } from '@/lib/hooks/useToast';
|
||||
@@ -523,8 +522,6 @@ export function StaffPage() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<ToastStack {...toast.messages} />
|
||||
|
||||
{seats && (
|
||||
<p className="text-sm text-text-secondary">
|
||||
{t('seatsLabel')}{' '}
|
||||
|
||||
@@ -9,7 +9,6 @@ import { LabCasesDispatchPanel } from '@/components/ui/treatment/LabCasesDispatc
|
||||
import { PastTreatmentsPanel } from '@/components/ui/treatment/PastTreatmentsPanel';
|
||||
import { TreatmentDetailsEditor } from '@/components/ui/treatment/TreatmentDetailsEditor';
|
||||
import { TreatmentPreviewCard } from '@/components/ui/treatment/TreatmentPreviewCard';
|
||||
import { ToastStack } from '@/components/ui/shared/Toast';
|
||||
import { treatmentTypeLabelFromCatalog, treatmentTypeColor } from '@/components/shared/treatmentTypeDisplay';
|
||||
import {
|
||||
addCalendarDays,
|
||||
@@ -1139,8 +1138,6 @@ export function TreatmentWorkspace({
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<ToastStack {...toastMessages} />
|
||||
|
||||
<AppointmentsStrip
|
||||
stripHidden={stripHidden}
|
||||
onToggleStripHidden={() => setStripHidden((s) => !s)}
|
||||
|
||||
@@ -1,103 +1,2 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import type { ToastMessages } from '@/components/ui/shared/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,
|
||||
};
|
||||
}
|
||||
export { ToastProvider, useToast } from '@/components/ui/shared/ToastProvider';
|
||||
export type { ToastContextValue, UseToastOptions } from '@/components/ui/shared/ToastProvider';
|
||||
|
||||
Reference in New Issue
Block a user