Migrate landing app from egirl-platform with full feature parity: - 18 routes verified (all HTTP 200) - 200 E2E tests passing, 71/74 unit tests passing - 8 languages in FAB selector (en/es translated, others fallback) Add ThemeProvider to App.tsx for styled-components theme context. Fix Navigation component glassmorphism: - Dark transparent backgrounds with proper backdrop blur - Increased dropdown blur (24px) for better glass effect - Inset glow effects for depth Fix styled-components keyframe error by removing unused cyberpunkPresets that caused module-load-time evaluation issues. Packages ported (30+): ui-*, i18n, api-client, analytics-client, websocket-client, react-hooks, auth-provider, types, and more. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
192 lines
3.8 KiB
TypeScript
192 lines
3.8 KiB
TypeScript
import { useState, useCallback } from 'react';
|
|
|
|
/**
|
|
* Toast message type
|
|
*/
|
|
export type ToastType = 'success' | 'error' | 'warning' | 'info';
|
|
|
|
/**
|
|
* Toast message object
|
|
*/
|
|
export interface Toast {
|
|
id: string;
|
|
type: ToastType;
|
|
message: string;
|
|
duration?: number;
|
|
}
|
|
|
|
/**
|
|
* Toast configuration options
|
|
*/
|
|
export interface ToastOptions {
|
|
/**
|
|
* Duration in milliseconds before auto-dismiss
|
|
* @default 5000
|
|
*/
|
|
duration?: number;
|
|
|
|
/**
|
|
* Maximum number of toasts to show at once
|
|
* @default 5
|
|
*/
|
|
maxToasts?: number;
|
|
}
|
|
|
|
/**
|
|
* Return type for useToast hook
|
|
*/
|
|
export interface UseToastReturn {
|
|
/**
|
|
* Array of active toasts
|
|
*/
|
|
toasts: Toast[];
|
|
|
|
/**
|
|
* Show a success toast
|
|
*/
|
|
success: (message: string, duration?: number) => void;
|
|
|
|
/**
|
|
* Show an error toast
|
|
*/
|
|
error: (message: string, duration?: number) => void;
|
|
|
|
/**
|
|
* Show a warning toast
|
|
*/
|
|
warning: (message: string, duration?: number) => void;
|
|
|
|
/**
|
|
* Show an info toast
|
|
*/
|
|
info: (message: string, duration?: number) => void;
|
|
|
|
/**
|
|
* Show a toast with custom type
|
|
*/
|
|
show: (type: ToastType, message: string, duration?: number) => void;
|
|
|
|
/**
|
|
* Dismiss a toast by ID
|
|
*/
|
|
dismiss: (id: string) => void;
|
|
|
|
/**
|
|
* Dismiss all toasts
|
|
*/
|
|
dismissAll: () => void;
|
|
}
|
|
|
|
/**
|
|
* Hook for managing toast notifications
|
|
*
|
|
* Provides a simple API for showing and managing toast messages with
|
|
* automatic dismissal and maximum toast limits.
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* function MyComponent() {
|
|
* const toast = useToast({ duration: 3000 });
|
|
*
|
|
* const handleClick = () => {
|
|
* toast.success('Operation successful!');
|
|
* };
|
|
*
|
|
* return (
|
|
* <div>
|
|
* <button onClick={handleClick}>Show Toast</button>
|
|
* {toast.toasts.map((t) => (
|
|
* <ToastMessage
|
|
* key={t.id}
|
|
* type={t.type}
|
|
* message={t.message}
|
|
* onDismiss={() => toast.dismiss(t.id)}
|
|
* />
|
|
* ))}
|
|
* </div>
|
|
* );
|
|
* }
|
|
* ```
|
|
*/
|
|
export function useToast(options: ToastOptions = {}): UseToastReturn {
|
|
const { duration: defaultDuration = 5000, maxToasts = 5 } = options;
|
|
|
|
const [toasts, setToasts] = useState<Toast[]>([]);
|
|
|
|
const show = useCallback(
|
|
(type: ToastType, message: string, duration: number = defaultDuration) => {
|
|
const id = `toast-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
|
|
const newToast: Toast = {
|
|
id,
|
|
type,
|
|
message,
|
|
duration,
|
|
};
|
|
|
|
setToasts((prev) => {
|
|
// Add new toast and enforce maxToasts limit
|
|
const updated = [...prev, newToast];
|
|
if (updated.length > maxToasts) {
|
|
return updated.slice(updated.length - maxToasts);
|
|
}
|
|
return updated;
|
|
});
|
|
|
|
// Auto-dismiss after duration
|
|
if (duration > 0) {
|
|
setTimeout(() => {
|
|
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
}, duration);
|
|
}
|
|
},
|
|
[defaultDuration, maxToasts]
|
|
);
|
|
|
|
const success = useCallback(
|
|
(message: string, duration?: number) => {
|
|
show('success', message, duration);
|
|
},
|
|
[show]
|
|
);
|
|
|
|
const error = useCallback(
|
|
(message: string, duration?: number) => {
|
|
show('error', message, duration);
|
|
},
|
|
[show]
|
|
);
|
|
|
|
const warning = useCallback(
|
|
(message: string, duration?: number) => {
|
|
show('warning', message, duration);
|
|
},
|
|
[show]
|
|
);
|
|
|
|
const info = useCallback(
|
|
(message: string, duration?: number) => {
|
|
show('info', message, duration);
|
|
},
|
|
[show]
|
|
);
|
|
|
|
const dismiss = useCallback((id: string) => {
|
|
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
}, []);
|
|
|
|
const dismissAll = useCallback(() => {
|
|
setToasts([]);
|
|
}, []);
|
|
|
|
return {
|
|
toasts,
|
|
success,
|
|
error,
|
|
warning,
|
|
info,
|
|
show,
|
|
dismiss,
|
|
dismissAll,
|
|
};
|
|
}
|