Phase 1: Progress modal text, SiteSerializer fields, Notification store, SiteCard checklist

- Improved progress modal messages in ai/engine.py (Section 4)
- Added keywords_count and has_integration to SiteSerializer (Section 6)
- Added notificationStore.ts for frontend notifications (Section 8)
- Added NotificationDropdownNew component (Section 8)
- Added SiteSetupChecklist to SiteCard in compact mode (Section 6)
- Updated api.ts Site interface with new fields
This commit is contained in:
IGNY8 VPS (Salman)
2025-12-27 17:40:28 +00:00
parent c44bee7fa7
commit a1ec3100fd
6 changed files with 606 additions and 33 deletions

View File

@@ -0,0 +1,205 @@
/**
* Notification Store
* Manages notifications for AI task completions and system events
*
* Features:
* - In-memory notification queue
* - Auto-dismissal with configurable timeout
* - Read/unread state tracking
* - Category-based filtering (ai_task, system, info)
*/
import { create } from 'zustand';
// ============================================================================
// TYPES
// ============================================================================
export type NotificationType = 'success' | 'error' | 'warning' | 'info';
export type NotificationCategory = 'ai_task' | 'system' | 'info';
export interface Notification {
id: string;
type: NotificationType;
category: NotificationCategory;
title: string;
message: string;
timestamp: Date;
read: boolean;
actionLabel?: string;
actionHref?: string;
metadata?: {
taskId?: string;
functionName?: string;
count?: number;
credits?: number;
};
}
interface NotificationStore {
notifications: Notification[];
unreadCount: number;
// Actions
addNotification: (notification: Omit<Notification, 'id' | 'timestamp' | 'read'>) => void;
markAsRead: (id: string) => void;
markAllAsRead: () => void;
removeNotification: (id: string) => void;
clearAll: () => void;
// AI Task specific
addAITaskNotification: (
functionName: string,
success: boolean,
message: string,
metadata?: Notification['metadata']
) => void;
}
// ============================================================================
// STORE IMPLEMENTATION
// ============================================================================
const generateId = () => `notif_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
export const useNotificationStore = create<NotificationStore>((set, get) => ({
notifications: [],
unreadCount: 0,
addNotification: (notification) => {
const newNotification: Notification = {
...notification,
id: generateId(),
timestamp: new Date(),
read: false,
};
set((state) => ({
notifications: [newNotification, ...state.notifications].slice(0, 50), // Keep last 50
unreadCount: state.unreadCount + 1,
}));
},
markAsRead: (id) => {
set((state) => ({
notifications: state.notifications.map((n) =>
n.id === id ? { ...n, read: true } : n
),
unreadCount: Math.max(0, state.notifications.filter(n => !n.read && n.id !== id).length),
}));
},
markAllAsRead: () => {
set((state) => ({
notifications: state.notifications.map((n) => ({ ...n, read: true })),
unreadCount: 0,
}));
},
removeNotification: (id) => {
set((state) => {
const notification = state.notifications.find(n => n.id === id);
const wasUnread = notification && !notification.read;
return {
notifications: state.notifications.filter((n) => n.id !== id),
unreadCount: wasUnread ? Math.max(0, state.unreadCount - 1) : state.unreadCount,
};
});
},
clearAll: () => {
set({ notifications: [], unreadCount: 0 });
},
addAITaskNotification: (functionName, success, message, metadata) => {
const displayNames: Record<string, string> = {
'auto_cluster': 'Keyword Clustering',
'generate_ideas': 'Idea Generation',
'generate_content': 'Content Generation',
'generate_images': 'Image Generation',
'generate_image_prompts': 'Image Prompts',
'optimize_content': 'Content Optimization',
};
const actionHrefs: Record<string, string> = {
'auto_cluster': '/planner/clusters',
'generate_ideas': '/planner/ideas',
'generate_content': '/writer/content',
'generate_images': '/writer/images',
'generate_image_prompts': '/writer/images',
'optimize_content': '/writer/content',
};
const title = displayNames[functionName] || functionName.replace(/_/g, ' ');
get().addNotification({
type: success ? 'success' : 'error',
category: 'ai_task',
title: success ? `${title} Complete` : `${title} Failed`,
message,
actionLabel: success ? 'View Results' : 'Retry',
actionHref: actionHrefs[functionName] || '/dashboard',
metadata: {
...metadata,
functionName,
},
});
},
}));
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
/**
* Format notification timestamp as relative time
*/
export function formatNotificationTime(timestamp: Date): string {
const now = new Date();
const diff = now.getTime() - timestamp.getTime();
const minutes = Math.floor(diff / 60000);
const hours = Math.floor(diff / 3600000);
const days = Math.floor(diff / 86400000);
if (minutes < 1) return 'Just now';
if (minutes < 60) return `${minutes}m ago`;
if (hours < 24) return `${hours}h ago`;
if (days < 7) return `${days}d ago`;
return timestamp.toLocaleDateString();
}
/**
* Get icon color classes for notification type
*/
export function getNotificationColors(type: NotificationType): {
bg: string;
icon: string;
border: string;
} {
const colors = {
success: {
bg: 'bg-green-50 dark:bg-green-900/20',
icon: 'text-green-500',
border: 'border-green-200 dark:border-green-800',
},
error: {
bg: 'bg-red-50 dark:bg-red-900/20',
icon: 'text-red-500',
border: 'border-red-200 dark:border-red-800',
},
warning: {
bg: 'bg-amber-50 dark:bg-amber-900/20',
icon: 'text-amber-500',
border: 'border-amber-200 dark:border-amber-800',
},
info: {
bg: 'bg-blue-50 dark:bg-blue-900/20',
icon: 'text-blue-500',
border: 'border-blue-200 dark:border-blue-800',
},
};
return colors[type];
}