From b293856ef2a7493cd6397e6d3e8166141854c34f Mon Sep 17 00:00:00 2001 From: "IGNY8 VPS (Salman)" Date: Fri, 21 Nov 2025 03:58:29 +0000 Subject: [PATCH] 1 --- .../CURRENT_WORKFLOW_STATUS.md | 29 +- backend/celerybeat-schedule | Bin 16384 -> 16384 bytes .../components/onboarding/WorkflowGuide.tsx | 9 +- .../sites/WordPressIntegrationForm.tsx | 448 ++++++++++++++++++ frontend/src/context/ThemeContext.tsx | 4 +- frontend/src/pages/Dashboard/Home.tsx | 2 +- frontend/src/pages/Sites/Settings.tsx | 60 +-- frontend/src/services/integration.api.ts | 16 +- frontend/vite.config.ts | 8 + 9 files changed, 515 insertions(+), 61 deletions(-) create mode 100644 frontend/src/components/sites/WordPressIntegrationForm.tsx diff --git a/active-workflow-docs/CURRENT_WORKFLOW_STATUS.md b/active-workflow-docs/CURRENT_WORKFLOW_STATUS.md index 19851f66..8c0dc9c8 100644 --- a/active-workflow-docs/CURRENT_WORKFLOW_STATUS.md +++ b/active-workflow-docs/CURRENT_WORKFLOW_STATUS.md @@ -128,6 +128,33 @@ PLANNING → WRITER → OPTIMIZE → PUBLISH ## Current Workflow Details +### Site Setup & Integration (New Flow) + +#### Step 1: Add Site (WordPress) +- User adds a new WordPress site from the welcome screen or sites page +- During site creation, user selects: + - **Industry**: From available industries (e.g., Technology, Healthcare, Finance) + - **Sectors**: Multiple sectors within the selected industry (e.g., SaaS, E-commerce, Mobile Apps) + - **Site Name**: Display name for the site + - **Website Address**: WordPress site URL +- Site is created with industry and sectors already configured + +#### Step 2: Integrate Site +- User is redirected to Site Settings → Integrations tab (`/sites/{id}/settings?tab=integrations`) +- **API Key Generation**: + - User generates a unique API key for the site + - API key is displayed and can be copied + - Key is stored securely in the integration credentials +- **Plugin Download**: + - User downloads the IGNY8 WP Bridge plugin directly from the page + - Plugin provides deeper WordPress integration than default WordPress app +- **WordPress Configuration**: + - User enters WordPress site URL + - User enters WordPress username + - User enters Application Password (created in WordPress admin) + - User enables/disables integration and two-way sync +- Integration is saved and connection is tested + ### Phase 1: Planning - Keyword import and management - Auto-clustering (1 credit per 30 keywords) @@ -146,7 +173,7 @@ PLANNING → WRITER → OPTIMIZE → PUBLISH - Apply improvements ### Phase 4: Publish -- WordPress publishing +- WordPress publishing (via IGNY8 WP Bridge plugin) - Site deployment (for IGNY8-hosted sites) - Content validation diff --git a/backend/celerybeat-schedule b/backend/celerybeat-schedule index c3259eb22393a7b420ad14c2a28fe228bf4d6e47..a040cce0b1fe1e29138af96d3e3aa67adf5eb697 100644 GIT binary patch delta 34 qcmZo@U~Fh$++b$LZ^XdB&^jeUv~5bz6yG;YlMPIiH)oj5-~<4@%nLmL delta 34 qcmZo@U~Fh$++b$LFUP>ZkTNAhv~5bz6yFz2lMPIiH)oj5-~<4 { + navigate(`/sites/${newSite.id}/settings?tab=integrations`); + }); } catch (error: any) { toast.error(`Failed to create site: ${error.message}`); } finally { diff --git a/frontend/src/components/sites/WordPressIntegrationForm.tsx b/frontend/src/components/sites/WordPressIntegrationForm.tsx new file mode 100644 index 00000000..aef3d126 --- /dev/null +++ b/frontend/src/components/sites/WordPressIntegrationForm.tsx @@ -0,0 +1,448 @@ +/** + * WordPress Integration Form Component + * Inline form for WordPress integration with API key generation and plugin download + */ +import React, { useState, useEffect } from 'react'; +import { Card } from '../ui/card'; +import Button from '../ui/button/Button'; +import Label from '../form/Label'; +import Input from '../form/input/Input'; +import Checkbox from '../form/input/Checkbox'; +import Alert from '../ui/alert/Alert'; +import { useToast } from '../ui/toast/ToastContainer'; +import { integrationApi, SiteIntegration } from '../../services/integration.api'; +import { + Globe, + Key, + Download, + Copy, + CheckCircle, + AlertCircle, + ExternalLink, + RefreshCw +} from 'lucide-react'; + +interface WordPressIntegrationFormProps { + siteId: number; + integration: SiteIntegration | null; + onIntegrationUpdate?: (integration: SiteIntegration) => void; +} + +export default function WordPressIntegrationForm({ + siteId, + integration, + onIntegrationUpdate, +}: WordPressIntegrationFormProps) { + const toast = useToast(); + const [loading, setLoading] = useState(false); + const [generatingKey, setGeneratingKey] = useState(false); + const [apiKey, setApiKey] = useState(''); + const [apiKeyVisible, setApiKeyVisible] = useState(false); + const [formData, setFormData] = useState({ + url: integration?.config_json?.site_url || '', + username: integration?.credentials_json?.username || '', + app_password: '', + is_active: integration?.is_active ?? true, + sync_enabled: integration?.sync_enabled ?? true, + }); + + useEffect(() => { + if (integration) { + setFormData({ + url: integration.config_json?.site_url || '', + username: integration.credentials_json?.username || '', + app_password: '', + is_active: integration.is_active ?? true, + sync_enabled: integration.sync_enabled ?? true, + }); + // Load API key if it exists + if (integration.credentials_json?.api_key) { + setApiKey(integration.credentials_json.api_key); + } + } + }, [integration]); + + const handleGenerateApiKey = async () => { + try { + setGeneratingKey(true); + // Generate API key - format: igny8_site_{siteId}_{timestamp}_{random} + // This will be stored in the integration credentials + const timestamp = Date.now(); + const random = Math.random().toString(36).substring(2, 15); + const token = `igny8_site_${siteId}_${timestamp}_${random}`; + setApiKey(token); + setApiKeyVisible(true); + toast.success('API key generated successfully. Make sure to save the integration to store this key.'); + } catch (error: any) { + toast.error(`Failed to generate API key: ${error.message}`); + } finally { + setGeneratingKey(false); + } + }; + + const handleCopyApiKey = () => { + if (apiKey) { + navigator.clipboard.writeText(apiKey); + toast.success('API key copied to clipboard'); + } + }; + + const handleDownloadPlugin = () => { + // TODO: Replace with actual plugin download URL + const pluginUrl = `https://github.com/igny8/igny8-wp-bridge/releases/latest/download/igny8-wp-bridge.zip`; + window.open(pluginUrl, '_blank'); + toast.success('Plugin download started'); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!formData.url || !formData.username || !formData.app_password) { + toast.error('Please fill in all required fields'); + return; + } + + if (!apiKey) { + toast.error('Please generate an API key first'); + return; + } + + try { + setLoading(true); + // Save integration with API key in credentials + const updatedIntegration = await integrationApi.saveWordPressIntegration(siteId, { + url: formData.url, + username: formData.username, + app_password: formData.app_password, + api_key: apiKey, // Include API key in credentials + is_active: formData.is_active, + sync_enabled: formData.sync_enabled, + }); + + if (onIntegrationUpdate) { + onIntegrationUpdate(updatedIntegration); + } + + toast.success('WordPress integration saved successfully'); + } catch (error: any) { + toast.error(`Failed to save integration: ${error.message}`); + } finally { + setLoading(false); + } + }; + + const handleTestConnection = async () => { + if (!integration?.id) { + toast.error('Please save the integration first'); + return; + } + + try { + setLoading(true); + const result = await integrationApi.testIntegration(integration.id); + if (result.success) { + toast.success('Connection test successful'); + } else { + toast.error(`Connection test failed: ${result.message}`); + } + } catch (error: any) { + toast.error(`Connection test failed: ${error.message}`); + } finally { + setLoading(false); + } + }; + + return ( +
+ {/* Header */} +
+
+ +
+
+

+ WordPress Integration +

+

+ Connect your WordPress site using the IGNY8 WP Bridge plugin +

+
+
+ + {/* Integration Guide */} + +
    +
  1. Generate an API key for your site
  2. +
  3. Download and install the IGNY8 WP Bridge plugin
  4. +
  5. Enter your WordPress site URL and credentials
  6. +
  7. Configure the plugin with your API key
  8. +
+
+ + {/* API Key Section */} + +
+
+
+

+ + API Key +

+

+ Generate an API key to authenticate the WordPress plugin with IGNY8 +

+
+ +
+ + {apiKey && ( +
+
+ +
+ + + +
+

+ Keep this key secure. You'll need it to configure the WordPress plugin. +

+
+
+ )} +
+
+ + {/* Plugin Download Section */} + {apiKey && ( + +
+
+
+

+ + IGNY8 WP Bridge Plugin +

+

+ Download and install the plugin on your WordPress site +

+
+ +
+ + +
+

Installation Instructions:

+
    +
  1. Download the plugin ZIP file
  2. +
  3. Go to WordPress Admin → Plugins → Add New
  4. +
  5. Click "Upload Plugin" and select the ZIP file
  6. +
  7. Activate the plugin
  8. +
  9. Go to Settings → IGNY8 Bridge and enter your API key
  10. +
+
+
+
+
+ )} + + {/* WordPress Credentials Form */} + +
+
+

+ WordPress Site Configuration +

+
+ +
+ + setFormData({ ...formData, url: e.target.value })} + placeholder="https://example.com" + required + className="mt-1" + /> +

+ Enter your WordPress site URL (with https://) +

+
+ +
+ + setFormData({ ...formData, username: e.target.value })} + placeholder="admin" + required + className="mt-1" + /> +

+ WordPress administrator username +

+
+ +
+ + setFormData({ ...formData, app_password: e.target.value })} + placeholder="xxxx xxxx xxxx xxxx xxxx xxxx" + required + className="mt-1" + /> +

+ Create an Application Password in WordPress: Users → Profile → Application Passwords +

+
+ +
+ setFormData({ ...formData, is_active: e.target.checked })} + label="Enable Integration" + /> + setFormData({ ...formData, sync_enabled: e.target.checked })} + label="Enable Two-Way Sync" + /> +
+ +
+ {integration && ( + + )} + +
+
+
+ + {/* Integration Status */} + {integration && ( + +
+

+ Integration Status +

+
+
+

Status

+
+ {integration.is_active ? ( + + ) : ( + + )} + + {integration.is_active ? 'Active' : 'Inactive'} + +
+
+
+

Sync Status

+
+ {integration.sync_status === 'success' ? ( + + ) : integration.sync_status === 'failed' ? ( + + ) : ( + + )} + + {integration.sync_status || 'Pending'} + +
+
+
+ {integration.last_sync_at && ( +
+

Last Sync

+

+ {new Date(integration.last_sync_at).toLocaleString()} +

+
+ )} +
+
+ )} +
+ ); +} + diff --git a/frontend/src/context/ThemeContext.tsx b/frontend/src/context/ThemeContext.tsx index 8432dbd3..d370fac9 100644 --- a/frontend/src/context/ThemeContext.tsx +++ b/frontend/src/context/ThemeContext.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useState, useContext, useEffect } from "react"; +import { createContext, useState, useContext, useEffect, type FC, type ReactNode } from "react"; type Theme = "light" | "dark"; @@ -9,7 +9,7 @@ type ThemeContextType = { const ThemeContext = createContext(undefined); -export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ +export const ThemeProvider: FC<{ children: ReactNode }> = ({ children, }) => { const [theme, setTheme] = useState("light"); diff --git a/frontend/src/pages/Dashboard/Home.tsx b/frontend/src/pages/Dashboard/Home.tsx index 886a639c..24ae4ef6 100644 --- a/frontend/src/pages/Dashboard/Home.tsx +++ b/frontend/src/pages/Dashboard/Home.tsx @@ -1187,7 +1187,7 @@ export default function Home() { - + 1 {/* Key Metrics */}
(null); const [wordPressIntegration, setWordPressIntegration] = useState(null); const [integrationLoading, setIntegrationLoading] = useState(false); - const [isIntegrationModalOpen, setIsIntegrationModalOpen] = useState(false); // Check for tab parameter in URL const initialTab = (searchParams.get('tab') as 'general' | 'seo' | 'og' | 'schema' | 'integrations') || 'general'; @@ -124,26 +122,11 @@ export default function SiteSettings() { } }; - const handleSaveIntegration = async (data: WordPressIntegrationFormData) => { - if (!siteId) return; - await integrationApi.saveWordPressIntegration(Number(siteId), data); + const handleIntegrationUpdate = async (integration: SiteIntegration) => { + setWordPressIntegration(integration); await loadIntegrations(); }; - const handleSyncIntegration = async () => { - if (!wordPressIntegration || !siteId) return; - try { - setIntegrationLoading(true); - await integrationApi.syncIntegration(wordPressIntegration.id); - toast.success('Content synced successfully'); - await loadIntegrations(); - } catch (error: any) { - toast.error(`Failed to sync: ${error.message}`); - } finally { - setIntegrationLoading(false); - } - }; - const handleSave = async () => { try { setSaving(true); @@ -553,17 +536,12 @@ export default function SiteSettings() { )} {/* Integrations Tab */} - {activeTab === 'integrations' && ( -
- setIsIntegrationModalOpen(true)} - onManage={() => setIsIntegrationModalOpen(true)} - onSync={handleSyncIntegration} - loading={integrationLoading} - siteId={siteId} - /> -
+ {activeTab === 'integrations' && siteId && ( + )} {/* Save Button */} @@ -576,26 +554,6 @@ export default function SiteSettings() { )}
- {/* WordPress Integration Modal */} - {siteId && ( - setIsIntegrationModalOpen(false)} - onSubmit={handleSaveIntegration} - siteId={Number(siteId)} - initialData={ - wordPressIntegration - ? { - url: wordPressIntegration.config_json?.site_url || '', - username: wordPressIntegration.credentials_json?.username || '', - app_password: '', // Never show password - is_active: wordPressIntegration.is_active, - sync_enabled: wordPressIntegration.sync_enabled, - } - : undefined - } - /> - )} ); } diff --git a/frontend/src/services/integration.api.ts b/frontend/src/services/integration.api.ts index 5533a18f..d05097af 100644 --- a/frontend/src/services/integration.api.ts +++ b/frontend/src/services/integration.api.ts @@ -116,12 +116,23 @@ export const integrationApi = { url: string; username: string; app_password: string; + api_key?: string; is_active?: boolean; sync_enabled?: boolean; } ): Promise { const existing = await this.getWordPressIntegration(siteId); + const credentials: Record = { + username: data.username, + app_password: data.app_password, + }; + + // Include API key if provided + if (data.api_key) { + credentials.api_key = data.api_key; + } + const integrationData: CreateIntegrationData = { site: siteId, platform: 'wordpress', @@ -129,10 +140,7 @@ export const integrationApi = { config_json: { site_url: data.url, }, - credentials_json: { - username: data.username, - app_password: data.app_password, - }, + credentials_json: credentials, is_active: data.is_active ?? true, sync_enabled: data.sync_enabled ?? true, }; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 331232a5..29bfacb2 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -106,6 +106,14 @@ export default defineConfig(({ mode, command }) => { }, }), ], + // Resolve configuration to prevent duplicate React instances + resolve: { + dedupe: ['react', 'react-dom'], + alias: { + react: resolve(__dirname, 'node_modules/react'), + 'react-dom': resolve(__dirname, 'node_modules/react-dom'), + }, + }, // Optimize dependency pre-bundling optimizeDeps: { include: [