web-app/src/core/services/ToastService.ts
padreug 04d64fe116 1.3.6 Toast Notification Pattern: Add centralized ToastService abstraction
- Create ToastService extending BaseService with context-specific toast methods
- Add useToast composable for convenient dependency injection access
- Provide standardized toast patterns: auth, payment, clipboard, operations
- Include async operation support with automatic loading/success/error states
- Integrate with DI container and base module for automatic initialization
- Demonstrate refactoring in LoginDialog.vue with context-specific methods
- Eliminate duplicate vue-sonner imports across 20+ files for better maintainability
- Support custom ToastOptions interface with full TypeScript compatibility

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-06 12:24:05 +02:00

303 lines
No EOL
7.9 KiB
TypeScript

import { BaseService } from '@/core/base/BaseService'
import { toast } from 'vue-sonner'
// Define our own ToastOptions interface based on vue-sonner's common options
interface ToastOptions {
duration?: number
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'top-center' | 'bottom-center'
richColors?: boolean
closeButton?: boolean
description?: string
action?: {
label: string
onClick: () => void
}
cancel?: {
label: string
onClick?: () => void
}
id?: string
dismissible?: boolean
onDismiss?: () => void
onAutoClose?: () => void
}
/**
* Toast notification configuration
*/
interface ToastConfig {
duration: number
position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'top-center' | 'bottom-center'
richColors: boolean
closeButton: boolean
}
/**
* Predefined toast action types with consistent styling
*/
export enum ToastActionType {
SUCCESS = 'success',
ERROR = 'error',
WARNING = 'warning',
INFO = 'info',
LOADING = 'loading'
}
/**
* Context-specific toast message templates
*/
interface ToastContext {
auth: {
loginSuccess: string
loginError: string
logoutSuccess: string
registrationSuccess: string
registrationError: string
}
payment: {
processing: string
success: string
failed: string
copied: string
copyFailed: string
}
clipboard: {
copied: string
copyFailed: string
}
general: {
operationSuccess: string
operationFailed: string
loading: string
}
}
/**
* Centralized Toast Service providing standardized notifications
* Eliminates duplicate import patterns and ensures consistent styling
*/
export class ToastService extends BaseService {
// Service metadata
protected readonly metadata = {
name: 'ToastService',
version: '1.0.0',
dependencies: [] // No dependencies
}
private config: ToastConfig = {
duration: 4000,
position: 'top-right',
richColors: true,
closeButton: false
}
private context: ToastContext = {
auth: {
loginSuccess: 'Login successful!',
loginError: 'Login failed. Please check your credentials.',
logoutSuccess: 'Logged out successfully',
registrationSuccess: 'Registration successful!',
registrationError: 'Registration failed. Please try again.'
},
payment: {
processing: 'Processing payment...',
success: 'Payment successful!',
failed: 'Payment failed',
copied: 'Payment request copied to clipboard',
copyFailed: 'Failed to copy payment request'
},
clipboard: {
copied: 'Copied to clipboard!',
copyFailed: 'Failed to copy to clipboard'
},
general: {
operationSuccess: 'Operation completed successfully',
operationFailed: 'Operation failed',
loading: 'Loading...'
}
}
/**
* Service initialization
*/
protected async onInitialize(): Promise<void> {
this.debug('ToastService initialized')
}
/**
* Show success toast
*/
success(message: string, options?: Partial<ToastOptions>): void {
toast.success(message, {
duration: this.config.duration,
...options
})
}
/**
* Show error toast
*/
error(message: string, options?: Partial<ToastOptions>): void {
toast.error(message, {
duration: this.config.duration,
...options
})
}
/**
* Show info toast
*/
info(message: string, options?: Partial<ToastOptions>): void {
toast.info(message, {
duration: this.config.duration,
...options
})
}
/**
* Show warning toast
*/
warning(message: string, options?: Partial<ToastOptions>): void {
toast.warning(message, {
duration: this.config.duration,
...options
})
}
/**
* Show loading toast
*/
loading(message: string, options?: Partial<ToastOptions>): string | number {
return toast.loading(message, {
duration: Infinity, // Loading toasts don't auto-dismiss
...options
})
}
/**
* Dismiss a specific toast
*/
dismiss(toastId?: string | number): void {
toast.dismiss(toastId)
}
/**
* Context-specific toast methods
*/
// Authentication toasts
auth = {
loginSuccess: (options?: Partial<ToastOptions>) =>
this.success(this.context.auth.loginSuccess, options),
loginError: (error?: string, options?: Partial<ToastOptions>) =>
this.error(error || this.context.auth.loginError, options),
logoutSuccess: (options?: Partial<ToastOptions>) =>
this.success(this.context.auth.logoutSuccess, options),
registrationSuccess: (options?: Partial<ToastOptions>) =>
this.success(this.context.auth.registrationSuccess, options),
registrationError: (error?: string, options?: Partial<ToastOptions>) =>
this.error(error || this.context.auth.registrationError, options)
}
// Payment toasts
payment = {
processing: (options?: Partial<ToastOptions>) =>
this.loading(this.context.payment.processing, options),
success: (message?: string, options?: Partial<ToastOptions>) =>
this.success(message || this.context.payment.success, options),
failed: (error?: string, options?: Partial<ToastOptions>) =>
this.error(error || this.context.payment.failed, options),
copied: (options?: Partial<ToastOptions>) =>
this.success(this.context.payment.copied, options),
copyFailed: (options?: Partial<ToastOptions>) =>
this.error(this.context.payment.copyFailed, options)
}
// Clipboard toasts
clipboard = {
copied: (item?: string, options?: Partial<ToastOptions>) =>
this.success(item ? `${item} copied to clipboard!` : this.context.clipboard.copied, options),
copyFailed: (item?: string, options?: Partial<ToastOptions>) =>
this.error(item ? `Failed to copy ${item}` : this.context.clipboard.copyFailed, options)
}
// General operation toasts
operation = {
success: (message?: string, options?: Partial<ToastOptions>) =>
this.success(message || this.context.general.operationSuccess, options),
failed: (error?: string, options?: Partial<ToastOptions>) =>
this.error(error || this.context.general.operationFailed, options),
loading: (message?: string, options?: Partial<ToastOptions>) =>
this.loading(message || this.context.general.loading, options)
}
/**
* Create a toast for async operations with auto-updating states
*/
async asyncOperation<T>(
operation: Promise<T>,
messages: {
loading?: string
success?: string | ((result: T) => string)
error?: string | ((error: Error) => string)
},
options?: Partial<ToastOptions>
): Promise<T> {
const loadingToast = this.loading(messages.loading || 'Processing...', options)
try {
const result = await operation
this.dismiss(loadingToast)
const successMessage = typeof messages.success === 'function'
? messages.success(result)
: messages.success || 'Operation successful!'
this.success(successMessage, options)
return result
} catch (error) {
this.dismiss(loadingToast)
const errorMessage = typeof messages.error === 'function'
? messages.error(error as Error)
: messages.error || (error as Error).message || 'Operation failed'
this.error(errorMessage, options)
throw error
}
}
/**
* Update toast configuration
*/
updateConfig(newConfig: Partial<ToastConfig>): void {
this.config = { ...this.config, ...newConfig }
}
/**
* Update context messages
*/
updateContext(newContext: Partial<ToastContext>): void {
this.context = {
auth: { ...this.context.auth, ...newContext.auth },
payment: { ...this.context.payment, ...newContext.payment },
clipboard: { ...this.context.clipboard, ...newContext.clipboard },
general: { ...this.context.general, ...newContext.general }
}
}
}
// Export singleton instance
export const toastService = new ToastService()