- 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>
303 lines
No EOL
7.9 KiB
TypeScript
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() |