/** * Content Plan Plugin - Main TypeScript Entry Point */ import React from 'react'; import { render } from '@wordpress/element'; import { useState, useEffect } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { ContentPlanProvider, useContentPlan, } from './context/ContentPlanContext'; import DynamicSettingsForm from './components/DynamicSettingsForm'; import ContentPlanTable from './components/ContentPlanTable'; import CalendarView from './components/CalendarView'; import ContentPlanGenerationModal from './components/ContentPlanGenerationModal'; import ContentPlanItemSidebar from './components/ContentPlanItemSidebar'; import TasksPanel from './components/TasksPanel'; import LogsTable from './components/LogsTable'; import Toast from './components/Toast'; import RecalculateScheduleModal from './components/RecalculateScheduleModal'; import OnboardingScreen from './components/OnboardingScreen'; import ErrorMessage from './components/ErrorMessage'; import Tooltip from './components/Tooltip'; import OptInPrompt from './components/OptInPrompt'; import { Tab, Settings, ContentPlanGenerationFormData } from './types'; import '../css/app.css'; const App: React.FC = () => { const [activeTab, setActiveTab] = useState('content-plan'); const [contentPlanView, setContentPlanView] = useState< 'table' | 'calendar' >('table'); const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' | 'info'; isVisible: boolean; }>({ message: '', type: 'success', isVisible: false, }); const [recalculateModal, setRecalculateModal] = useState<{ isOpen: boolean; futureItemsCount: number; oldInterval: number; newInterval: number; }>({ isOpen: false, futureItemsCount: 0, oldInterval: 7, newInterval: 7, }); const { loading, error, data, user, usage, usageError, backendConfigured, settings, fieldConfigs, strings, clearError, refreshData, saveSettings, recalculateSchedule, getString, generationModalOpen, openGenerationModal, closeGenerationModal, currentTaskId, tasks, tasksLoading, sidebarOpen, opted, optinUrl, } = useContentPlan(); // Check if tutorial should be shown const showTutorial = settings && settings.tutorial_watched !== true; // Define tooltip order const tooltipOrder = [ 'content-plan-header', 'generate-ai', 'schedule-manual', 'usage-box', ]; // State to track current tooltip const [currentTooltipIndex, setCurrentTooltipIndex] = useState(0); const currentTooltipId = showTutorial && currentTooltipIndex < tooltipOrder.length ? tooltipOrder[currentTooltipIndex] : null; // Function to advance to next tooltip const nextTooltip = () => { // Small delay to allow fade-out animation to complete setTimeout(() => { if (currentTooltipIndex < tooltipOrder.length - 1) { setCurrentTooltipIndex(prev => prev + 1); } else { // All tooltips shown, mark tutorial as watched markTutorialWatched(); } }, 200); // Match fade-out animation duration }; // Function to skip tutorial const skipTutorial = () => { markTutorialWatched(); }; // Function to mark tutorial as watched const markTutorialWatched = async () => { try { await saveSettings({ ...settings, tutorial_watched: true, }); setCurrentTooltipIndex(tooltipOrder.length); // Stop showing tooltips } catch (error) { console.error('Failed to mark tutorial as watched:', error); } }; // Reset tooltip index when tutorial becomes available useEffect(() => { if (showTutorial) { setCurrentTooltipIndex(0); } }, [showTutorial]); const tabs: Tab[] = [ { id: 'content-plan', label: getString('navigation', 'content_plan', 'Content Plan'), }, { id: 'settings', label: getString('navigation', 'settings', 'Settings'), }, { id: 'log', label: getString('navigation', 'log', 'Log') }, ]; const handleOnboardingComplete = () => { // Refresh data to get updated settings refreshData(); }; const handleOnboardingSkip = () => { // Settings are already updated optimistically in OnboardingScreen // Refresh other data in the background without blocking the dashboard refreshData(); }; // Check if onboarding is needed // Show onboarding if onboarding_completed is not explicitly true // (i.e., it's false or undefined/not set) // BUT skip onboarding if opted === false (user has opted out of Freemius) const needsOnboarding = settings && settings.onboarding_completed !== true && opted !== false; // Check if onboarding is completed - if so, show dashboard immediately even while loading const onboardingCompleted = settings && settings.onboarding_completed === true; // Show loader while loading OR while settings haven't been populated yet // BUT skip loading spinner if onboarding is already completed (to allow immediate dashboard display after skip) if ( (loading || !settings || Object.keys(settings).length === 0) && !onboardingCompleted ) { return (
{getString('app', 'loading', 'Loading...')}
); } // Show onboarding screen if needed (after loading is complete) if (needsOnboarding) { return ( ); } const renderTabContent = (): JSX.Element | null => { // Don't show error in main content area if sidebar is open // (error will be shown in the sidebar instead) if (error && !sidebarOpen) { return (
); } switch (activeTab) { case 'content-plan': return (

{getString( 'content_plan_table', 'title', 'Content Plan' )}

{getString( 'content_plan_table', 'description', 'Manage your content plans with the table below. Create, edit, and delete content plans as needed.' )}

{/* Tasks Panel - shown when there are tasks, loading tasks, or a current task */} {(() => { const unwatchedTasks = tasks.filter( task => !task.watched ); const hasActiveTasks = unwatchedTasks.some( task => task.status === 'queued' || task.status === 'processing' ); const hasAnyTasks = unwatchedTasks.length > 0; // Show panel if: loading tasks, has active tasks, has any tasks, or currentTaskId is set const shouldShowPanel = tasksLoading || currentTaskId || hasActiveTasks || hasAnyTasks; return shouldShowPanel ? (
{ // onClose will be handled by TasksPanel to clear currentTaskId }} />
) : null; })()} {/* Horizontal Tabs for Table/Calendar View */}
{/* Render selected view */} {contentPlanView === 'table' ? ( ) : ( )}
); case 'settings': return (

{getString('settings', 'title', 'Settings')}

{getString( 'settings', 'description', 'Configure your plugin settings below' )}

{fieldConfigs && Object.keys(fieldConfigs).length > 0 ? ( { try { const result = await saveSettings(formData); // Check if recalculation is needed if (result?.recalculation_needed) { setRecalculateModal({ isOpen: true, futureItemsCount: result.future_items_count || 0, oldInterval: result.old_interval || 7, newInterval: result.new_interval || 7, }); } else { // Show success toast notification setToast({ message: getString( 'settings', 'save_success', 'Settings saved successfully' ), type: 'success', isVisible: true, }); } } catch (error) { console.error( 'Failed to save settings:', error ); // Show error toast notification setToast({ message: getString( 'settings', 'save_error', 'Failed to save settings' ), type: 'error', isVisible: true, }); } }} /> ) : (

{getString( 'settings', 'no_settings_config', 'No settings configuration available.' )}

)}
); case 'log': return (

{getString('log', 'title', 'Log')}

{getString( 'log', 'description', 'Activity logs will be available here.' )}

); default: return null; } }; // Show onboarding screen if needed (after loading is complete) if (needsOnboarding) { return ( ); } // Show main dashboard (onboarding completed or not needed) return (
{/* Main Layout - Responsive: Vertical on mobile, Horizontal on desktop */}
{/* Navigation - Horizontal tabs on mobile, Vertical sidebar on desktop */}
{/* Usage Information - Displayed below navigation menu */}

{getString('common', 'usage', 'Usage')}

{loading ? (
{getString( 'app', 'loading', 'Loading...' )}
) : opted === false ? ( ) : usageError ? (
) : usage ? (
{getString( 'common', 'account_status', 'Account Status' )} : {usage.blocked ? getString( 'common', 'blocked', 'Blocked' ) : getString( 'common', 'active', 'Active' )}
{getString( 'common', 'tokens_remaining', 'Tokens Remaining' )} : 0 ? 'text-green-600' : 'text-red-600' }`} > {usage.tokensRemaining}
{usage.renewDate && (
{getString( 'common', 'renew_date', 'Renew Date' )} : {new Date( usage.renewDate ).toLocaleDateString()}
)} {usage.blocked && (
{getString( 'common', 'account_blocked', 'Account is blocked' )}
)}
) : backendConfigured ? (

{getString( 'common', 'usage_not_available', 'Usage information is not available at this time.' )}

) : null}
{/* Right Column - Tab Content */}
{renderTabContent()}
{/* Content Plan Generation Modal */} {/* Toast Notification */} setToast(prev => ({ ...prev, isVisible: false })) } duration={3000} /> {/* Recalculate Schedule Modal */} setRecalculateModal(prev => ({ ...prev, isOpen: false })) } onConfirm={async () => { try { await recalculateSchedule(); setToast({ message: getString( 'settings', 'recalculate_success', 'Scheduled dates recalculated successfully' ), type: 'success', isVisible: true, }); } catch (error) { console.error('Failed to recalculate schedule:', error); setToast({ message: getString( 'settings', 'recalculate_error', 'Failed to recalculate scheduled dates' ), type: 'error', isVisible: true, }); } }} futureItemsCount={recalculateModal.futureItemsCount} oldInterval={recalculateModal.oldInterval} newInterval={recalculateModal.newInterval} /> {/* Content Plan Item Sidebar - Available for both table and calendar views */}
); }; // Initialize the app when DOM is ready document.addEventListener('DOMContentLoaded', () => { const appContainer = document.getElementById('cpepai-app'); if (appContainer) { render( , appContainer ); } }); // Export for potential external use export { App };