From 0d3e25e50fa80c7af2de013f00a18057bc6dbcc9 Mon Sep 17 00:00:00 2001 From: "IGNY8 VPS (Salman)" Date: Thu, 1 Jan 2026 01:40:34 +0000 Subject: [PATCH] autoamtiona nd other pages udpates, --- .../automation/services/automation_service.py | 168 +++++++- .../Automation/CurrentProcessingCardV2.tsx | 88 ++-- .../Automation/GlobalProgressBar.tsx | 88 ++-- frontend/src/components/common/PageHeader.tsx | 7 +- .../src/pages/Automation/AutomationPage.tsx | 399 +++++++++++------- frontend/src/pages/Help/Help.tsx | 19 +- .../src/pages/account/AccountSettingsPage.tsx | 80 ++-- .../src/pages/account/NotificationsPage.tsx | 42 +- .../src/pages/account/PlansAndBillingPage.tsx | 67 ++- .../src/pages/account/PurchaseCreditsPage.tsx | 116 ++--- .../src/pages/account/UsageAnalyticsPage.tsx | 61 +-- 11 files changed, 701 insertions(+), 434 deletions(-) diff --git a/backend/igny8_core/business/automation/services/automation_service.py b/backend/igny8_core/business/automation/services/automation_service.py index d7dac651..c46bce64 100644 --- a/backend/igny8_core/business/automation/services/automation_service.py +++ b/backend/igny8_core/business/automation/services/automation_service.py @@ -152,10 +152,12 @@ class AutomationService: start_time = time.time() # Query pending keywords + # FIXED: Match pipeline_overview query - use status='new' only + # Keywords with status='new' are ready for clustering, regardless of cluster FK + # (If cluster FK is set but status='new', it means the old cluster was deleted) pending_keywords = Keywords.objects.filter( site=self.site, status='new', - cluster__isnull=True, disabled=False ) @@ -411,10 +413,10 @@ class AutomationService: start_time = time.time() # ADDED: Pre-stage validation - verify Stage 1 completion + # FIXED: Match pipeline_overview query - use status='new' only pending_keywords = Keywords.objects.filter( site=self.site, status='new', - cluster__isnull=True, disabled=False ).count() @@ -1138,6 +1140,26 @@ class AutomationService: self.run.run_id, self.account.id, self.site.id, stage_number, f"Content '{content.title}' complete ({content_processed}/{total_content})" ) + + # ADDED: Incremental save after each content piece for real-time frontend progress + # This allows the frontend to show accurate progress during Stage 5 + current_prompts_created = Images.objects.filter( + site=self.site, + status='pending', + created_at__gte=self.run.started_at + ).count() + current_credits_used = self._get_credits_used() - credits_before + current_time_elapsed = self._format_time_elapsed(start_time) + self.run.stage_5_result = { + 'content_processed': content_processed, + 'content_total': total_content, + 'prompts_created': current_prompts_created, + 'credits_used': current_credits_used, + 'time_elapsed': current_time_elapsed, + 'in_progress': True + } + self.run.save(update_fields=['stage_5_result']) + except Exception as e: # FIXED: Log error but continue processing remaining content error_msg = f"Failed to extract prompts for content '{content.title}': {str(e)}" @@ -1441,9 +1463,14 @@ class AutomationService: time.sleep(delay) def run_stage_7(self): - """Stage 7: Manual Review Gate (Count Only)""" + """Stage 7: Auto-Approve and Publish Review Content + + This stage automatically approves content in 'review' status and + marks it as 'published' (or queues for WordPress sync). + """ stage_number = 7 - stage_name = "Manual Review Gate" + stage_name = "Review → Published" + start_time = time.time() # Query content ready for review ready_for_review = Content.objects.filter( @@ -1452,7 +1479,6 @@ class AutomationService: ) total_count = ready_for_review.count() - content_ids = list(ready_for_review.values_list('id', flat=True)) # Log stage start self.logger.log_stage_start( @@ -1460,22 +1486,129 @@ class AutomationService: stage_number, stage_name, total_count ) - self.logger.log_stage_progress( - self.run.run_id, self.account.id, self.site.id, - stage_number, f"Automation complete. {total_count} content pieces ready for review" - ) - - if content_ids: + if total_count == 0: self.logger.log_stage_progress( self.run.run_id, self.account.id, self.site.id, - stage_number, f"Content IDs ready: {content_ids[:10]}..." if len(content_ids) > 10 else f"Content IDs ready: {content_ids}" + stage_number, "No content in review to approve - completing automation" ) + self.run.stage_7_result = { + 'ready_for_review': 0, + 'approved_count': 0, + 'content_ids': [] + } + self.run.status = 'completed' + self.run.completed_at = datetime.now() + self.run.save() + cache.delete(f'automation_lock_{self.site.id}') + return - # Save results + content_list = list(ready_for_review) + approved_count = 0 + + # INITIAL SAVE: Set totals immediately self.run.stage_7_result = { 'ready_for_review': total_count, - 'content_ids': content_ids + 'review_total': total_count, + 'approved_count': 0, + 'content_ids': [], + 'in_progress': True } + self.run.save(update_fields=['stage_7_result']) + + for idx, content in enumerate(content_list, 1): + # Check if automation should stop (paused or cancelled) + should_stop, reason = self._check_should_stop() + if should_stop: + self.logger.log_stage_progress( + self.run.run_id, self.account.id, self.site.id, + stage_number, f"Stage {reason} - saving progress ({approved_count} content approved)" + ) + time_elapsed = self._format_time_elapsed(start_time) + self.run.stage_7_result = { + 'ready_for_review': total_count, + 'review_total': total_count, + 'approved_count': approved_count, + 'content_ids': list(Content.objects.filter( + site=self.site, status='published', updated_at__gte=self.run.started_at + ).values_list('id', flat=True)), + 'partial': True, + 'stopped_reason': reason, + 'time_elapsed': time_elapsed + } + self.run.save() + return + + try: + self.logger.log_stage_progress( + self.run.run_id, self.account.id, self.site.id, + stage_number, f"Approving content {idx}/{total_count}: {content.title}" + ) + + # Approve content by changing status to 'published' + content.status = 'published' + content.save(update_fields=['status', 'updated_at']) + + approved_count += 1 + + self.logger.log_stage_progress( + self.run.run_id, self.account.id, self.site.id, + stage_number, f"Content '{content.title}' approved ({approved_count}/{total_count})" + ) + + # Incremental save for real-time frontend progress + current_time_elapsed = self._format_time_elapsed(start_time) + self.run.stage_7_result = { + 'ready_for_review': total_count, + 'review_total': total_count, + 'approved_count': approved_count, + 'content_ids': [], # Don't store full list during processing + 'time_elapsed': current_time_elapsed, + 'in_progress': True + } + self.run.save(update_fields=['stage_7_result']) + + except Exception as e: + error_msg = f"Failed to approve content '{content.title}': {str(e)}" + logger.error(f"[AutomationService] {error_msg}", exc_info=True) + self.logger.log_stage_error( + self.run.run_id, self.account.id, self.site.id, + stage_number, error_msg + ) + continue + + # Small delay between approvals to prevent overwhelming the system + if idx < total_count: + time.sleep(0.5) + + # Final results + time_elapsed = self._format_time_elapsed(start_time) + content_ids = list(Content.objects.filter( + site=self.site, + status='published', + updated_at__gte=self.run.started_at + ).values_list('id', flat=True)) + + self.logger.log_stage_complete( + self.run.run_id, self.account.id, self.site.id, + stage_number, approved_count, time_elapsed, 0 + ) + + self.run.stage_7_result = { + 'ready_for_review': total_count, + 'review_total': total_count, + 'approved_count': approved_count, + 'content_ids': content_ids, + 'time_elapsed': time_elapsed, + 'in_progress': False + } + self.run.status = 'completed' + self.run.completed_at = datetime.now() + self.run.save() + + # Release lock + cache.delete(f'automation_lock_{self.site.id}') + + logger.info(f"[AutomationService] Stage 7 complete: {approved_count} content pieces approved and published") self.run.status = 'completed' self.run.completed_at = datetime.now() self.run.save() @@ -1501,8 +1634,8 @@ class AutomationService: def estimate_credits(self) -> int: """Estimate total credits needed for automation""" - # Count items - keywords_count = Keywords.objects.filter(site=self.site, status='new', cluster__isnull=True, disabled=False).count() + # Count items - FIXED: Match pipeline_overview query + keywords_count = Keywords.objects.filter(site=self.site, status='new', disabled=False).count() clusters_count = Clusters.objects.filter(site=self.site, status='new').exclude(ideas__isnull=False).count() ideas_count = ContentIdeas.objects.filter(site=self.site, status='new').count() tasks_count = Tasks.objects.filter(site=self.site, status='queued').count() @@ -1526,8 +1659,9 @@ class AutomationService: This snapshot is used to calculate global progress percentage correctly. """ # Stage 1: Keywords pending clustering + # FIXED: Match pipeline_overview query - use status='new' only stage_1_initial = Keywords.objects.filter( - site=self.site, status='new', cluster__isnull=True, disabled=False + site=self.site, status='new', disabled=False ).count() # Stage 2: Clusters needing ideas diff --git a/frontend/src/components/Automation/CurrentProcessingCardV2.tsx b/frontend/src/components/Automation/CurrentProcessingCardV2.tsx index fdc5c98a..08223036 100644 --- a/frontend/src/components/Automation/CurrentProcessingCardV2.tsx +++ b/frontend/src/components/Automation/CurrentProcessingCardV2.tsx @@ -191,7 +191,7 @@ const CurrentProcessingCard: React.FC = ({ }; return ( -
= ({ {/* Header Row */}
-
{isPaused ? ( - + ) : ( - + )}
-

+

{isPaused ? 'Automation Paused' : 'Automation In Progress'}

-
- Stage {currentRun.current_stage}: {stageName} - + + Stage {currentRun.current_stage}: {stageName} + + {stageOverview?.type || 'AI'} @@ -225,13 +227,14 @@ const CurrentProcessingCard: React.FC = ({
- {/* Close and Actions */} -
- -
+ {/* Close Button - Top Right */} +
{/* Progress Section */} @@ -241,37 +244,38 @@ const CurrentProcessingCard: React.FC = ({ {/* Progress Text */}
- + {displayPercent}% - + {realProcessed}/{realTotal} {outputLabel}
- + {realTotal - realProcessed} remaining
- {/* Progress Bar */} -
+ {/* Progress Bar - Vibrant */} +
- {/* Control Buttons */} + {/* Control Buttons - Clean Design */}
{currentRun.status === 'running' ? ( ) : null} - +
{/* Metrics - 30% width */}
-
+
- Duration + Duration
- {formatDuration(currentRun.started_at)} + {formatDuration(currentRun.started_at)}
-
+
- Credits + Credits
- {currentRun.total_credits_used} + {currentRun.total_credits_used}
-
- Stage - {currentRun.current_stage} of 7 +
+ Stage + {currentRun.current_stage} of 7
-
- Status - +
+ Status + {isPaused ? 'Paused' : 'Running'}
diff --git a/frontend/src/components/Automation/GlobalProgressBar.tsx b/frontend/src/components/Automation/GlobalProgressBar.tsx index cae9a36b..c69aaa2d 100644 --- a/frontend/src/components/Automation/GlobalProgressBar.tsx +++ b/frontend/src/components/Automation/GlobalProgressBar.tsx @@ -7,15 +7,15 @@ import React from 'react'; import { AutomationRun, InitialSnapshot, StageProgress, GlobalProgress } from '../../services/automationService'; import { BoltIcon, CheckCircleIcon, PauseIcon } from '../../icons'; -// Stage colors matching AutomationPage STAGE_CONFIG +// Stage colors matching AutomationPage STAGE_CONFIG exactly const STAGE_COLORS = [ - 'from-brand-500 to-brand-600', // Stage 1: Keywords → Clusters - 'from-purple-500 to-purple-600', // Stage 2: Clusters → Ideas - 'from-purple-500 to-purple-600', // Stage 3: Ideas → Tasks - 'from-success-500 to-success-600', // Stage 4: Tasks → Content - 'from-warning-500 to-warning-600', // Stage 5: Content → Image Prompts - 'from-purple-500 to-purple-600', // Stage 6: Image Prompts → Images - 'from-success-500 to-success-600', // Stage 7: Manual Review Gate + 'from-brand-500 to-brand-600', // Stage 1: Keywords → Clusters (brand/teal) + 'from-purple-500 to-purple-600', // Stage 2: Clusters → Ideas (purple) + 'from-warning-500 to-warning-600', // Stage 3: Ideas → Tasks (amber) + 'from-brand-500 to-brand-600', // Stage 4: Tasks → Content (brand/teal) + 'from-success-500 to-success-600', // Stage 5: Content → Image Prompts (green) + 'from-purple-500 to-purple-600', // Stage 6: Image Prompts → Images (purple) + 'from-success-500 to-success-600', // Stage 7: Review Gate (green) ]; const STAGE_NAMES = [ @@ -25,7 +25,7 @@ const STAGE_NAMES = [ 'Tasks', 'Content', 'Prompts', - 'Review', + 'Publish', ]; // Helper to get processed count from stage result using correct key @@ -76,13 +76,9 @@ const GlobalProgressBar: React.FC = ({ // Fallback: Calculate from currentRun and initialSnapshot const snapshot = initialSnapshot || (currentRun as any)?.initial_snapshot; - if (!snapshot) { - return { percentage: 0, completed: 0, total: 0 }; - } - const totalInitial = snapshot.total_initial_items || 0; + // Calculate total completed from all stage results let totalCompleted = 0; - for (let i = 1; i <= 7; i++) { const result = (currentRun as any)[`stage_${i}_result`]; if (result) { @@ -90,12 +86,26 @@ const GlobalProgressBar: React.FC = ({ } } - const percentage = totalInitial > 0 ? Math.round((totalCompleted / totalInitial) * 100) : 0; + // Calculate total items - sum of ALL stage initials from snapshot (updated after each stage) + // This accounts for items created during the run (e.g., keywords create clusters, clusters create ideas) + let totalItems = 0; + if (snapshot) { + for (let i = 1; i <= 7; i++) { + const stageInitial = snapshot[`stage_${i}_initial`] || 0; + totalItems += stageInitial; + } + } + + // Use the updated total from snapshot, or fallback to total_initial_items + const finalTotal = totalItems > 0 ? totalItems : (snapshot?.total_initial_items || 0); + + // Ensure completed never exceeds total (clamp percentage to 100%) + const percentage = finalTotal > 0 ? Math.round((totalCompleted / finalTotal) * 100) : 0; return { percentage: Math.min(percentage, 100), completed: totalCompleted, - total: totalInitial, + total: finalTotal, }; }; @@ -139,46 +149,46 @@ const GlobalProgressBar: React.FC = ({ return (
{/* Header Row */} -
+
{isPaused ? ( - + ) : percentage >= 100 ? ( - + ) : ( - + )}
-
+
{isPaused ? 'Pipeline Paused' : 'Full Pipeline Progress'}
-
+
Stage {currentStage} of 7 • {formatDuration()}
-
+
{percentage}%
- {/* Segmented Progress Bar */} -
+ {/* Segmented Progress Bar - Taller & More Vibrant */} +
{[1, 2, 3, 4, 5, 6, 7].map(stageNum => { const status = getStageStatus(stageNum); const stageColor = STAGE_COLORS[stageNum - 1]; @@ -188,9 +198,9 @@ const GlobalProgressBar: React.FC = ({ key={stageNum} className={`flex-1 transition-all duration-500 relative group ${ status === 'completed' - ? `bg-gradient-to-r ${stageColor}` + ? `bg-gradient-to-r ${stageColor} shadow-sm` : status === 'active' - ? `bg-gradient-to-r ${stageColor} opacity-60 ${!isPaused ? 'animate-pulse' : ''}` + ? `bg-gradient-to-r ${stageColor} opacity-70 ${!isPaused ? 'animate-pulse' : ''} shadow-sm` : 'bg-gray-300 dark:bg-gray-600' }`} title={`Stage ${stageNum}: ${STAGE_NAMES[stageNum - 1]}`} @@ -206,20 +216,22 @@ const GlobalProgressBar: React.FC = ({ })}
- {/* Footer Row */} -
- {completed} / {total} items processed -
+ {/* Footer Row - Larger Font for Stage Numbers */} +
+ {completed} / {total} items processed +
{[1, 2, 3, 4, 5, 6, 7].map(stageNum => { const status = getStageStatus(stageNum); return ( {stageNum} {status === 'completed' && '✓'} diff --git a/frontend/src/components/common/PageHeader.tsx b/frontend/src/components/common/PageHeader.tsx index fb20e164..eedce095 100644 --- a/frontend/src/components/common/PageHeader.tsx +++ b/frontend/src/components/common/PageHeader.tsx @@ -116,11 +116,8 @@ export default function PageHeader({ return (
- {/* Title now shown in AppHeader - this component only triggers the context update */} - {/* Show description if provided - can be used for additional context */} - {description && ( -

{description}

- )} + {/* Title and description now shown in AppHeader - this component only triggers the context update */} + {/* Description rendering removed - visible in AppHeader tooltip or not needed */}
); } diff --git a/frontend/src/pages/Automation/AutomationPage.tsx b/frontend/src/pages/Automation/AutomationPage.tsx index 4e66e3f5..0ec9f27e 100644 --- a/frontend/src/pages/Automation/AutomationPage.tsx +++ b/frontend/src/pages/Automation/AutomationPage.tsx @@ -55,7 +55,7 @@ const STAGE_CONFIG = [ { icon: CheckCircleIcon, color: 'from-brand-500 to-brand-600', textColor: 'text-brand-600 dark:text-brand-400', bgColor: 'bg-brand-100 dark:bg-brand-900/30', hoverColor: 'hover:border-brand-500', name: 'Tasks → Content' }, { icon: PencilIcon, color: 'from-success-500 to-success-600', textColor: 'text-success-600 dark:text-success-400', bgColor: 'bg-success-100 dark:bg-success-900/30', hoverColor: 'hover:border-success-500', name: 'Content → Image Prompts' }, { icon: FileIcon, color: 'from-purple-500 to-purple-600', textColor: 'text-purple-600 dark:text-purple-400', bgColor: 'bg-purple-100 dark:bg-purple-900/30', hoverColor: 'hover:border-purple-500', name: 'Image Prompts → Images' }, - { icon: PaperPlaneIcon, color: 'from-success-500 to-success-600', textColor: 'text-success-600 dark:text-success-400', bgColor: 'bg-success-100 dark:bg-success-900/30', hoverColor: 'hover:border-success-500', name: 'Review Gate' }, + { icon: PaperPlaneIcon, color: 'from-success-500 to-success-600', textColor: 'text-success-600 dark:text-success-400', bgColor: 'bg-success-100 dark:bg-success-900/30', hoverColor: 'hover:border-success-500', name: 'Review → Published' }, ]; const AutomationPage: React.FC = () => { @@ -292,7 +292,22 @@ const AutomationPage: React.FC = () => { toast.success('Automation started successfully'); loadCurrentRun(); } catch (error: any) { - toast.error(error.response?.data?.error || 'Failed to start automation'); + const errorMsg = error.response?.data?.error || 'Failed to start automation'; + // Use informational messages for "no work to do" cases, errors for system/account issues + const isInfoMessage = + errorMsg.toLowerCase().includes('no keywords') || + errorMsg.toLowerCase().includes('no pending') || + errorMsg.toLowerCase().includes('nothing to process') || + errorMsg.toLowerCase().includes('no items') || + errorMsg.toLowerCase().includes('all stages') || + errorMsg.toLowerCase().includes('minimum') || + errorMsg.toLowerCase().includes('at least'); + + if (isInfoMessage) { + toast.info(errorMsg); + } else { + toast.error(errorMsg); + } } }; @@ -382,7 +397,7 @@ const AutomationPage: React.FC = () => { const visible = items.filter(i => i && (i.value !== undefined && i.value !== null)); if (visible.length === 0) { return ( -
+
@@ -392,11 +407,11 @@ const AutomationPage: React.FC = () => { if (visible.length === 2) { return ( -
+
{visible.map((it, idx) => (
- {it.label} -
{Number(it.value) || 0}
+ {it.label} +
{Number(it.value) || 0}
))}
@@ -405,11 +420,11 @@ const AutomationPage: React.FC = () => { // default to 3 columns (equally spaced) return ( -
+
{items.concat(Array(Math.max(0, 3 - items.length)).fill({ label: '', value: '' })).slice(0,3).map((it, idx) => (
- {it.label} -
{it.value !== undefined && it.value !== null ? Number(it.value) : ''}
+ {it.label} +
{it.value !== undefined && it.value !== null ? Number(it.value) : ''}
))}
@@ -431,7 +446,7 @@ const AutomationPage: React.FC = () => { {/* Compact Schedule & Controls Panel */} {config && ( - +
@@ -539,11 +554,11 @@ const AutomationPage: React.FC = () => { {/* Keywords */}
-
-
- +
+
+
-
Keywords
+
Keywords
{(() => { const res = getStageResult(1); @@ -571,11 +586,11 @@ const AutomationPage: React.FC = () => { {/* Clusters */}
-
-
- +
+
+
-
Clusters
+
Clusters
{(() => { const res = getStageResult(2); @@ -603,11 +618,11 @@ const AutomationPage: React.FC = () => { {/* Ideas */}
-
-
- +
+
+
-
Ideas
+
Ideas
{(() => { const res = getStageResult(3); @@ -637,11 +652,11 @@ const AutomationPage: React.FC = () => { {/* Content */}
-
-
- +
+
+
-
Content
+
Content
{(() => { const res = getStageResult(4); @@ -671,11 +686,11 @@ const AutomationPage: React.FC = () => { {/* Images */}
-
-
- +
+
+
-
Images
+
Images
{(() => { const res = getStageResult(6); @@ -782,7 +797,7 @@ const AutomationPage: React.FC = () => {
{ } `} > - {/* Compact Header */} -
-
-
-
Stage {stage.number}
- {isActive && ● Active} - {isComplete && } - {!isActive && !isComplete && stage.pending > 0 && Ready} + {/* Header Row - Icon, Stage Number, Status on left; Function Name on right */} +
+
+
+
-
{stageConfig.name}
- {stageConfig.description && ( -
{stageConfig.description}
- )} + Stage {stage.number} + {isActive && ● Active} + {isComplete && } + {!isActive && !isComplete && stage.pending > 0 && Ready}
-
- + {/* Stage Function Name - Right side, larger font */} +
{stageConfig.name}
+
+ + {/* Single Row: Pending & Processed - Larger Font */} +
+
+
Pending
+
0 ? stageConfig.textColor : 'text-gray-400 dark:text-gray-500'}`}> + {pending} +
+
+
+
+
Processed
+
0 ? 'text-success-600 dark:text-success-400' : 'text-gray-400 dark:text-gray-500'}`}> + {processed} +
- {/* Simplified Queue Metrics: Only Pending & Processed */} -
-
- Pending: - 0 ? stageConfig.textColor : 'text-gray-400 dark:text-gray-500'}`}> - {pending} - + {/* Credits and Duration - only show during/after run */} + {result && (result.credits_used > 0 || result.time_elapsed) && ( +
+ {result.credits_used > 0 && ( + {result.credits_used} credits + )} + {result.time_elapsed && ( + {result.time_elapsed} + )}
-
- Processed: - 0 ? 'text-success-600 dark:text-success-400' : 'text-gray-400 dark:text-gray-500'}`}> - {processed} - -
- {/* Credits and Duration - only show during/after run */} - {result && (result.credits_used > 0 || result.time_elapsed) && ( -
- {result.credits_used > 0 && ( - {result.credits_used} credits - )} - {result.time_elapsed && ( - {result.time_elapsed} - )} -
- )} -
+ )} - {/* Progress Bar */} + {/* Progress Bar with breathing circle indicator */} {(isActive || isComplete || processed > 0) && ( -
-
- Progress +
+
+
+ Progress + {isActive && ( + + + + + )} + {isComplete && ( + + + + )} +
{isComplete ? '100' : progressPercent}%
-
+
@@ -895,7 +921,7 @@ const AutomationPage: React.FC = () => {
{ } `} > -
-
-
-
Stage {stage.number}
- {isActive && ● Active} - {isComplete && } - {!isActive && !isComplete && stage.pending > 0 && Ready} + {/* Header Row - Icon, Stage Number, Status on left; Function Name on right */} +
+
+
+
-
{stageConfig.name}
- {stageConfig.description && ( -
{stageConfig.description}
- )} + Stage {stage.number} + {isActive && ● Active} + {isComplete && } + {!isActive && !isComplete && stage.pending > 0 && Ready}
-
- + {/* Stage Function Name - Right side, larger font */} +
{stageConfig.name}
+
+ + {/* Single Row: Pending & Processed - Larger Font */} +
+
+
Pending
+
0 ? stageConfig.textColor : 'text-gray-400 dark:text-gray-500'}`}> + {pending} +
+
+
+
+
Processed
+
0 ? 'text-success-600 dark:text-success-400' : 'text-gray-400 dark:text-gray-500'}`}> + {processed} +
- {/* Simplified Queue Metrics: Only Pending & Processed */} -
-
- Pending: - 0 ? stageConfig.textColor : 'text-gray-400 dark:text-gray-500'}`}> - {pending} - + {/* Credits and Duration - only show during/after run */} + {result && (result.credits_used > 0 || result.time_elapsed) && ( +
+ {result.credits_used > 0 && ( + {result.credits_used} credits + )} + {result.time_elapsed && ( + {result.time_elapsed} + )}
-
- Processed: - 0 ? 'text-success-600 dark:text-success-400' : 'text-gray-400 dark:text-gray-500'}`}> - {processed} - -
- {/* Credits and Duration - only show during/after run */} - {result && (result.credits_used > 0 || result.time_elapsed) && ( -
- {result.credits_used > 0 && ( - {result.credits_used} credits - )} - {result.time_elapsed && ( - {result.time_elapsed} - )} -
- )} -
+ )} + {/* Progress Bar with breathing circle indicator */} {(isActive || isComplete || processed > 0) && ( -
-
- Progress +
+
+
+ Progress + {isActive && ( + + + + + )} + {isComplete && ( + + + + )} +
{isComplete ? '100' : progressPercent}%
-
+
@@ -969,62 +1008,119 @@ const AutomationPage: React.FC = () => { ); })} - {/* Stage 7 - Manual Review Gate */} + {/* Stage 7 - Review → Published (Auto-approve) */} {pipelineOverview[6] && (() => { const stage7 = pipelineOverview[6]; + const stageConfig = STAGE_CONFIG[6]; const isActive = currentRun?.current_stage === 7; - const isComplete = currentRun && currentRun.current_stage > 7; + const isComplete = currentRun && currentRun.status === 'completed'; + const result = currentRun ? (currentRun[`stage_7_result` as keyof AutomationRun] as any) : null; + + // For stage 7: pending = items in review, processed = items approved + const approvedCount = result?.approved_count ?? 0; + const totalReview = result?.review_total ?? result?.ready_for_review ?? stage7.pending ?? 0; + const pendingReview = isActive || isComplete + ? Math.max(0, totalReview - approvedCount) + : stage7.pending ?? 0; + + const progressPercent = totalReview > 0 ? Math.min(Math.round((approvedCount / totalReview) * 100), 100) : 0; return (
0 - ? 'border-warning-300 bg-warning-50 dark:bg-warning-900/20 dark:border-warning-700' + : pendingReview > 0 + ? 'border-success-300 bg-success-50 dark:bg-success-900/20 dark:border-success-700' : 'border-gray-200 bg-gray-50 dark:bg-white/[0.02] dark:border-gray-800' } `} > -
-
-
-
Stage 7
- 🚫 Stop + {/* Header Row - Icon, Stage Number, Status on left; Function Name on right */} +
+
+
+
-
Manual Review Gate
+ Stage 7 + {isActive && ● Active} + {isComplete && } + {!isActive && !isComplete && pendingReview > 0 && Ready}
-
- + {/* Stage Function Name - Right side, larger font */} +
{stageConfig.name}
+
+ + {/* Single Row: Pending & Approved */} +
+
+
Pending
+
0 ? stageConfig.textColor : 'text-gray-400 dark:text-gray-500'}`}> + {pendingReview} +
+
+
+
+
Approved
+
0 ? 'text-success-600 dark:text-success-400' : 'text-gray-400 dark:text-gray-500'}`}> + {approvedCount} +
- {/* Simplified: Just show the count, no buttons */} -
-
{stage7.pending}
-
ready for review
-
+ {/* Progress Bar with breathing circle indicator */} + {(isActive || isComplete || approvedCount > 0) && ( +
+
+
+ Progress + {isActive && ( + + + + + )} + {isComplete && ( + + + + )} +
+ {isComplete ? '100' : progressPercent}% +
+
+
+
+
+ )}
); })()} - {/* Approved summary card (placed after Stage 7 in the same row) */} -
-
-
-
- + {/* Approved summary card - Same layout as Stage 7 */} +
+ {/* Header Row - Icon and Label on left, Big Count on right */} +
+
+
+
-
Approved
+ Approved +
+ {/* Big count on right */} +
+ {metrics?.content?.published ?? pipelineOverview[3]?.counts?.published ?? getStageResult(4)?.published ?? 0}
-
 
-
-
-
{metrics?.content?.published ?? pipelineOverview[3]?.counts?.published ?? getStageResult(4)?.published ?? 0}
+ + {/* Status Label - Right aligned */} +
Published Content
@@ -1045,5 +1141,4 @@ const AutomationPage: React.FC = () => { ); }; -export default AutomationPage; - +export default AutomationPage; \ No newline at end of file diff --git a/frontend/src/pages/Help/Help.tsx b/frontend/src/pages/Help/Help.tsx index 9e8a2024..269bd05f 100644 --- a/frontend/src/pages/Help/Help.tsx +++ b/frontend/src/pages/Help/Help.tsx @@ -1,5 +1,6 @@ import { useState, useRef, useEffect } from "react"; import PageMeta from "../../components/common/PageMeta"; +import PageHeader from "../../components/common/PageHeader"; import { Accordion, AccordionItem } from "../../components/ui/accordion"; import { Card } from "../../components/ui/card"; import Badge from "../../components/ui/badge/Badge"; @@ -9,7 +10,8 @@ import { CheckCircleIcon, ArrowRightIcon, FileIcon, - GroupIcon + GroupIcon, + HelpCircleIcon } from "../../icons"; interface TableOfContentsItem { @@ -188,18 +190,13 @@ export default function Help() { return ( <> + , color: 'blue' }} + />
- {/* Header */} -
-

- Help Center -

-

- Learn how to use IGNY8 to create and publish amazing content -

-
- {/* Table of Contents */}

diff --git a/frontend/src/pages/account/AccountSettingsPage.tsx b/frontend/src/pages/account/AccountSettingsPage.tsx index 45bf9943..5d41bf5c 100644 --- a/frontend/src/pages/account/AccountSettingsPage.tsx +++ b/frontend/src/pages/account/AccountSettingsPage.tsx @@ -13,6 +13,7 @@ import { Card } from '../../components/ui/card'; import Button from '../../components/ui/button/Button'; import Badge from '../../components/ui/badge/Badge'; import PageMeta from '../../components/common/PageMeta'; +import PageHeader from '../../components/common/PageHeader'; import { useToast } from '../../components/ui/toast/ToastContainer'; import { useAuthStore } from '../../store/authStore'; import { @@ -265,47 +266,42 @@ export default function AccountSettingsPage() { if (loading) { return ( -
+ <> -
-
- -
Loading settings...
+ , color: 'blue' }} + /> +
+
+
+ +
Loading settings...
+
-
+ ); } - return ( -
- - - {/* Page Header with Breadcrumb */} -
-
- Account Settings - - - {activeTab === 'account' && 'Account'} - {activeTab === 'profile' && 'Profile'} - {activeTab === 'team' && 'Team'} - -
-

- {activeTab === 'account' && 'Account Information'} - {activeTab === 'profile' && 'Profile Settings'} - {activeTab === 'team' && 'Team Management'} -

-

- {activeTab === 'account' && 'Manage your organization and billing information'} - {activeTab === 'profile' && 'Update your personal information and preferences'} - {activeTab === 'team' && 'Invite and manage team members'} -

-
+ // Page titles based on active tab + const pageTitles = { + account: { title: 'Account Information', description: 'Manage your organization and billing information' }, + profile: { title: 'Profile Settings', description: 'Update your personal information and preferences' }, + team: { title: 'Team Management', description: 'Invite and manage team members' }, + }; - {/* Tab Content */} -
+ return ( + <> + + , color: 'blue' }} + parent="Account Settings" + /> +
+ {/* Tab Content */} {/* Account Tab */} {activeTab === 'account' && (
@@ -762,15 +758,14 @@ export default function AccountSettingsPage() {
)} -
- {/* Invite Modal */} - {showInviteModal && ( -
- -

- Invite Team Member -

+ {/* Invite Modal */} + {showInviteModal && ( +
+ +

+ Invite Team Member +

@@ -921,5 +916,6 @@ export default function AccountSettingsPage() {
)}
+ ); } diff --git a/frontend/src/pages/account/NotificationsPage.tsx b/frontend/src/pages/account/NotificationsPage.tsx index a8bbd757..c165044f 100644 --- a/frontend/src/pages/account/NotificationsPage.tsx +++ b/frontend/src/pages/account/NotificationsPage.tsx @@ -15,6 +15,8 @@ import { } from 'lucide-react'; import { Card } from '../../components/ui/card'; import Button from '../../components/ui/button/Button'; +import PageMeta from '../../components/common/PageMeta'; +import PageHeader from '../../components/common/PageHeader'; import { useNotificationStore } from '../../store/notificationStore'; import type { NotificationAPI } from '../../services/notifications.api'; import { deleteNotification as deleteNotificationAPI } from '../../services/notifications.api'; @@ -177,28 +179,30 @@ export default function NotificationsPage() { return ( <> - - Notifications - IGNY8 - + + , color: 'blue' }} + /> -
- {/* Header */} -
-
-

- Notifications -

-

- {unreadCount > 0 ? ( - - {unreadCount} unread notification{unreadCount !== 1 ? 's' : ''} +

+ {/* Action Buttons */} +
+ {/* Unread count indicator */} +
+ {unreadCount > 0 ? ( + + + {unreadCount} - ) : ( - 'All caught up!' - )} -

+ unread notification{unreadCount !== 1 ? 's' : ''} +
+ ) : ( + ✓ All caught up! + )}
- +
+ <> + + , color: 'blue' }} + /> +
+
+ +
+
+ ); } @@ -365,28 +376,23 @@ export default function PlansAndBillingPage() { }; return ( -
- {/* Page Header with Breadcrumb */} -
-
- Plans & Billing - - {pageTitles[activeTab].title} -
-

{pageTitles[activeTab].title}

-

- {pageTitles[activeTab].description} -

-
- - {/* Activation / pending payment notice */} - {!hasActivePlan && ( -
- No active plan. Choose a plan below to activate your account. -
- )} - {hasPendingManualPayment && ( -
+ <> + + , color: 'blue' }} + parent="Plans & Billing" + /> +
+ {/* Activation / pending payment notice */} + {!hasActivePlan && ( +
+ No active plan. Choose a plan below to activate your account. +
+ )} + {hasPendingManualPayment && ( +
We received your manual payment. It’s pending admin approval; activation will complete once approved.
)} @@ -587,14 +593,6 @@ export default function PlansAndBillingPage() {
{/* Upgrade Plans Section */}
-
-

- {hasActivePlan ? 'Upgrade or Change Your Plan' : 'Choose Your Plan'} -

-

- Select the plan that best fits your needs -

-
)} -
+
+ ); } \ No newline at end of file diff --git a/frontend/src/pages/account/PurchaseCreditsPage.tsx b/frontend/src/pages/account/PurchaseCreditsPage.tsx index 28efaa1c..de7de9f0 100644 --- a/frontend/src/pages/account/PurchaseCreditsPage.tsx +++ b/frontend/src/pages/account/PurchaseCreditsPage.tsx @@ -4,8 +4,10 @@ */ import { useState, useEffect } from 'react'; -import { AlertCircle, Check, CreditCard, Building2, Wallet, Loader2 } from 'lucide-react'; +import { AlertCircle, Check, CreditCard, Building2, Wallet, Loader2, Zap } from 'lucide-react'; import Button from '../../components/ui/button/Button'; +import PageMeta from '../../components/common/PageMeta'; +import PageHeader from '../../components/common/PageHeader'; import { getCreditPackages, getAvailablePaymentMethods, @@ -142,9 +144,18 @@ export default function PurchaseCreditsPage() { if (loading) { return ( -
- -
+ <> + + , color: 'blue' }} + /> +
+
+ +
+
+ ); } @@ -152,25 +163,30 @@ export default function PurchaseCreditsPage() { const selectedMethod = paymentMethods.find((m) => m.type === selectedPaymentMethod); return ( -
-

Complete Payment

- - {/* Invoice Details */} -
-

Invoice Details

-
-
- Invoice Number: - {invoiceData.invoice_number} -
-
- Package: - {selectedPackage?.name} -
-
- Credits: - {selectedPackage?.credits.toLocaleString()} -
+ <> + + , color: 'blue' }} + parent="Purchase Credits" + /> +
+ {/* Invoice Details */} +
+

Invoice Details

+
+
+ Invoice Number: + {invoiceData.invoice_number} +
+
+ Package: + {selectedPackage?.name} +
+
+ Credits: + {selectedPackage?.credits.toLocaleString()} +
Total Amount: ${invoiceData.total_amount} @@ -285,35 +301,38 @@ export default function PurchaseCreditsPage() {
-
+
+ ); } return ( -
-
-

Purchase Credits

-

- Choose a credit package and payment method to top up your account balance. -

+ <> + + , color: 'blue' }} + /> +
+
+ {error && ( +
+ +

{error}

+
+ )} - {error && ( -
- -

{error}

-
- )} - - {/* Credit Packages */} -
-

Select Credit Package

-
- {packages.map((pkg) => ( -
setSelectedPackage(pkg)} - className={`relative cursor-pointer rounded-lg border-2 p-6 transition-all ${ - selectedPackage?.id === pkg.id + {/* Credit Packages */} +
+

Select Credit Package

+
+ {packages.map((pkg) => ( +
setSelectedPackage(pkg)} + className={`relative cursor-pointer rounded-lg border-2 p-6 transition-all ${ + selectedPackage?.id === pkg.id ? 'border-[var(--color-brand-500)] bg-[var(--color-brand-50)]' : 'border-gray-200 hover:border-[var(--color-brand-300)] bg-white' } ${pkg.is_featured ? 'ring-2 ring-warning-400' : ''}`} @@ -424,7 +443,8 @@ export default function PurchaseCreditsPage() {
)} +
-
+ ); } diff --git a/frontend/src/pages/account/UsageAnalyticsPage.tsx b/frontend/src/pages/account/UsageAnalyticsPage.tsx index bb655064..425f0060 100644 --- a/frontend/src/pages/account/UsageAnalyticsPage.tsx +++ b/frontend/src/pages/account/UsageAnalyticsPage.tsx @@ -8,6 +8,7 @@ import { useState, useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import { TrendingUp, Activity, BarChart3, Zap, Calendar } from 'lucide-react'; import PageMeta from '../../components/common/PageMeta'; +import PageHeader from '../../components/common/PageHeader'; import { useToast } from '../../components/ui/toast/ToastContainer'; import { getUsageAnalytics, UsageAnalytics, getCreditBalance, type CreditBalance } from '../../services/billing.api'; import { Card } from '../../components/ui/card'; @@ -57,15 +58,21 @@ export default function UsageAnalyticsPage() { if (loading) { return ( -
+ <> -
-
-
-
Loading usage data...
+ , color: 'blue' }} + /> +
+
+
+
+
Loading usage data...
+
-
+ ); } @@ -75,28 +82,27 @@ export default function UsageAnalyticsPage() { api: 'Activity Log', }; - return ( -
- - - {/* Page Header */} -
-
- Usage & Analytics / {tabTitles[activeTab]} -
-

{tabTitles[activeTab]}

-

- {activeTab === 'limits' && 'See how much you\'re using - Track your credits and content limits'} - {activeTab === 'activity' && 'See where your credits go - Track credit usage history'} - {activeTab === 'api' && 'Technical requests - Monitor API activity and usage'} -

-
+ const tabDescriptions: Record = { + limits: 'See how much you\'re using - Track your credits and content limits', + activity: 'See where your credits go - Track credit usage history', + api: 'Technical requests - Monitor API activity and usage', + }; - {/* Quick Stats Overview */} - {creditBalance && ( -
- -
+ return ( + <> + + , color: 'blue' }} + parent="Usage & Analytics" + /> +
+ {/* Quick Stats Overview */} + {creditBalance && ( +
+ +
@@ -276,5 +282,6 @@ export default function UsageAnalyticsPage() { )}
+ ); }