From 155a73d92876017609a2bad0a1cc5292ee378715 Mon Sep 17 00:00:00 2001 From: "IGNY8 VPS (Salman)" Date: Tue, 18 Nov 2025 04:28:51 +0000 Subject: [PATCH] Enhance Site Settings with WordPress Integration - Updated SiteSettings component to include a new 'Integrations' tab for managing WordPress integration. - Added functionality to load, save, and sync WordPress integration settings. - Integrated WordPressIntegrationCard and WordPressIntegrationModal for user interaction. - Implemented URL parameter handling for tab navigation. --- backend/celerybeat-schedule | Bin 16384 -> 16384 bytes .../sites/WordPressIntegrationCard.tsx | 144 ++++++++++++++ .../sites/WordPressIntegrationModal.tsx | 177 ++++++++++++++++++ frontend/src/pages/Sites/Settings.tsx | 117 +++++++++++- frontend/src/services/integration.api.ts | 146 +++++++++++++++ 5 files changed, 576 insertions(+), 8 deletions(-) create mode 100644 frontend/src/components/sites/WordPressIntegrationCard.tsx create mode 100644 frontend/src/components/sites/WordPressIntegrationModal.tsx create mode 100644 frontend/src/services/integration.api.ts diff --git a/backend/celerybeat-schedule b/backend/celerybeat-schedule index 2a1032aa3ab71bda6fa081cc5c6a1603f2dc2c01..4f12d06baddc571157f649531616b3baea09e223 100644 GIT binary patch delta 29 kcmZo@U~Fh$+@NH_C&IwMP&6e&v~5bz6z|T>1|}D{0fWN{bN~PV delta 29 kcmZo@U~Fh$+@NH_$IrmP;65cov~5bz6z`7B1|}D{0f5;FLjV8( diff --git a/frontend/src/components/sites/WordPressIntegrationCard.tsx b/frontend/src/components/sites/WordPressIntegrationCard.tsx new file mode 100644 index 00000000..04323f84 --- /dev/null +++ b/frontend/src/components/sites/WordPressIntegrationCard.tsx @@ -0,0 +1,144 @@ +/** + * WordPress Integration Card Component + * Displays WordPress integration status and quick actions + */ +import React from 'react'; +import { Globe, CheckCircle, XCircle, Settings, RefreshCw } from 'lucide-react'; +import { Card } from '../ui/card'; +import Button from '../ui/button/Button'; +import Badge from '../ui/badge/Badge'; + +interface WordPressIntegration { + id: number; + site: number; + platform: string; + is_active: boolean; + sync_enabled: boolean; + sync_status: 'success' | 'failed' | 'pending'; + last_sync_at?: string; + config_json?: { + url?: string; + username?: string; + }; +} + +interface WordPressIntegrationCardProps { + integration: WordPressIntegration | null; + onConnect: () => void; + onManage: () => void; + onSync?: () => void; + loading?: boolean; +} + +export default function WordPressIntegrationCard({ + integration, + onConnect, + onManage, + onSync, + loading = false, +}: WordPressIntegrationCardProps) { + if (!integration) { + return ( + +
+
+
+ +
+
+

+ WordPress Integration +

+

+ Connect your WordPress site to sync content +

+
+
+ +
+
+ ); + } + + return ( + +
+
+
+
+ +
+
+

+ WordPress Integration +

+

+ {integration.config_json?.url || 'WordPress Site'} +

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

Sync Status

+
+ {integration.sync_status === 'success' ? ( + + ) : integration.sync_status === 'failed' ? ( + + ) : ( + + )} + + {integration.sync_status} + +
+
+
+

Last Sync

+

+ {integration.last_sync_at + ? new Date(integration.last_sync_at).toLocaleDateString() + : 'Never'} +

+
+
+ +
+ + {onSync && ( + + )} +
+
+
+ ); +} + diff --git a/frontend/src/components/sites/WordPressIntegrationModal.tsx b/frontend/src/components/sites/WordPressIntegrationModal.tsx new file mode 100644 index 00000000..46343c7e --- /dev/null +++ b/frontend/src/components/sites/WordPressIntegrationModal.tsx @@ -0,0 +1,177 @@ +/** + * WordPress Integration Modal Component + * Form for connecting/managing WordPress integration + */ +import React, { useState } from 'react'; +import { X, Globe, AlertCircle } from 'lucide-react'; +import Modal from '../ui/modal/Modal'; +import Button from '../ui/button/Button'; +import Label from '../form/Label'; +import Input from '../form/input/Input'; +import Checkbox from '../form/input/Checkbox'; +import { useToast } from '../ui/toast/ToastContainer'; + +interface WordPressIntegrationModalProps { + isOpen: boolean; + onClose: () => void; + onSubmit: (data: WordPressIntegrationFormData) => Promise; + initialData?: WordPressIntegrationFormData; + siteId: number; +} + +export interface WordPressIntegrationFormData { + url: string; + username: string; + app_password: string; + is_active: boolean; + sync_enabled: boolean; +} + +export default function WordPressIntegrationModal({ + isOpen, + onClose, + onSubmit, + initialData, + siteId, +}: WordPressIntegrationModalProps) { + const toast = useToast(); + const [loading, setLoading] = useState(false); + const [formData, setFormData] = useState({ + url: initialData?.url || '', + username: initialData?.username || '', + app_password: initialData?.app_password || '', + is_active: initialData?.is_active ?? true, + sync_enabled: initialData?.sync_enabled ?? true, + }); + + 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; + } + + try { + setLoading(true); + await onSubmit(formData); + toast.success('WordPress integration saved successfully'); + onClose(); + } catch (error: any) { + toast.error(`Failed to save integration: ${error.message}`); + } finally { + setLoading(false); + } + }; + + return ( + +
+
+
+
+ +
+

+ {initialData ? 'Edit WordPress Integration' : 'Connect WordPress Site'} +

+
+ +
+ +
+
+
+ +
+

WordPress Application Password Required

+

+ You need to create an Application Password in WordPress. Go to Users → Profile → + Application Passwords to generate one. +

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

+ Enter your WordPress site URL (without trailing slash) +

+
+ +
+ + setFormData({ ...formData, username: e.target.value })} + placeholder="admin" + required + /> +
+ +
+ + setFormData({ ...formData, app_password: e.target.value })} + placeholder="xxxx xxxx xxxx xxxx xxxx xxxx" + required + /> +

+ Enter the application password (with spaces or without) +

+
+ +
+ setFormData({ ...formData, is_active: e.target.checked })} + label="Enable Integration" + /> + setFormData({ ...formData, sync_enabled: e.target.checked })} + label="Enable Two-Way Sync" + /> +
+ +
+ + +
+
+
+
+ ); +} + diff --git a/frontend/src/pages/Sites/Settings.tsx b/frontend/src/pages/Sites/Settings.tsx index e09f73f2..fd0240af 100644 --- a/frontend/src/pages/Sites/Settings.tsx +++ b/frontend/src/pages/Sites/Settings.tsx @@ -4,8 +4,8 @@ * Features: SEO (meta tags, Open Graph, schema.org) */ import React, { useState, useEffect } from 'react'; -import { useParams, useNavigate } from 'react-router-dom'; -import { SettingsIcon, SearchIcon, Share2Icon, CodeIcon } from 'lucide-react'; +import { useParams, useNavigate, useSearchParams } from 'react-router-dom'; +import { SettingsIcon, SearchIcon, Share2Icon, CodeIcon, PlugIcon } from 'lucide-react'; import PageMeta from '../../components/common/PageMeta'; import { Card } from '../../components/ui/card'; import Button from '../../components/ui/button/Button'; @@ -15,16 +15,25 @@ import Checkbox from '../../components/form/input/Checkbox'; import TextArea from '../../components/form/input/TextArea'; import { useToast } from '../../components/ui/toast/ToastContainer'; import { fetchAPI } from '../../services/api'; +import WordPressIntegrationCard from '../../components/sites/WordPressIntegrationCard'; +import WordPressIntegrationModal, { WordPressIntegrationFormData } from '../../components/sites/WordPressIntegrationModal'; +import { integrationApi, SiteIntegration } from '../../services/integration.api'; export default function SiteSettings() { const { siteId } = useParams<{ siteId: string }>(); const navigate = useNavigate(); + const [searchParams] = useSearchParams(); const toast = useToast(); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [site, setSite] = useState(null); + const [wordPressIntegration, setWordPressIntegration] = useState(null); + const [integrationLoading, setIntegrationLoading] = useState(false); + const [isIntegrationModalOpen, setIsIntegrationModalOpen] = useState(false); - const [activeTab, setActiveTab] = useState<'general' | 'seo' | 'og' | 'schema'>('general'); + // Check for tab parameter in URL + const initialTab = (searchParams.get('tab') as 'general' | 'seo' | 'og' | 'schema' | 'integrations') || 'general'; + const [activeTab, setActiveTab] = useState<'general' | 'seo' | 'og' | 'schema' | 'integrations'>(initialTab); const [formData, setFormData] = useState({ name: '', slug: '', @@ -51,9 +60,18 @@ export default function SiteSettings() { useEffect(() => { if (siteId) { loadSite(); + loadIntegrations(); } }, [siteId]); + useEffect(() => { + // Update tab if URL parameter changes + const tab = searchParams.get('tab'); + if (tab && ['general', 'seo', 'og', 'schema', 'integrations'].includes(tab)) { + setActiveTab(tab as typeof activeTab); + } + }, [searchParams]); + const loadSite = async () => { try { setLoading(true); @@ -91,6 +109,40 @@ export default function SiteSettings() { } }; + const loadIntegrations = async () => { + if (!siteId) return; + try { + setIntegrationLoading(true); + const integration = await integrationApi.getWordPressIntegration(Number(siteId)); + setWordPressIntegration(integration); + } catch (error: any) { + // Integration might not exist, that's okay + setWordPressIntegration(null); + } finally { + setIntegrationLoading(false); + } + }; + + const handleSaveIntegration = async (data: WordPressIntegrationFormData) => { + if (!siteId) return; + await integrationApi.saveWordPressIntegration(Number(siteId), data); + 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); @@ -219,6 +271,18 @@ export default function SiteSettings() { Schema.org + @@ -475,12 +539,49 @@ export default function SiteSettings() { )} -
- -
+ {/* Integrations Tab */} + {activeTab === 'integrations' && ( +
+ setIsIntegrationModalOpen(true)} + onManage={() => setIsIntegrationModalOpen(true)} + onSync={handleSyncIntegration} + loading={integrationLoading} + /> +
+ )} + + {/* Save Button */} + {activeTab !== 'integrations' && ( +
+ +
+ )} + + {/* WordPress Integration Modal */} + {siteId && ( + setIsIntegrationModalOpen(false)} + onSubmit={handleSaveIntegration} + siteId={Number(siteId)} + initialData={ + wordPressIntegration + ? { + url: wordPressIntegration.config_json?.url || '', + username: wordPressIntegration.config_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 new file mode 100644 index 00000000..4d64382b --- /dev/null +++ b/frontend/src/services/integration.api.ts @@ -0,0 +1,146 @@ +/** + * Integration API Service + * Handles all integration-related API calls + */ +import { fetchAPI } from './api'; + +export interface SiteIntegration { + id: number; + site: number; + platform: 'wordpress' | 'shopify' | 'custom'; + platform_type: 'cms' | 'ecommerce' | 'custom_api'; + config_json: Record; + is_active: boolean; + sync_enabled: boolean; + last_sync_at?: string; + sync_status: 'success' | 'failed' | 'pending'; + created_at: string; + updated_at: string; +} + +export interface CreateIntegrationData { + site: number; + platform: 'wordpress' | 'shopify' | 'custom'; + platform_type?: 'cms' | 'ecommerce' | 'custom_api'; + config_json: Record; + credentials?: Record; + is_active?: boolean; + sync_enabled?: boolean; +} + +export const integrationApi = { + /** + * Get all integrations for a site + */ + async getSiteIntegrations(siteId: number): Promise { + const response = await fetchAPI(`/v1/integration/integrations/?site=${siteId}`); + return response?.results || response || []; + }, + + /** + * Get integration by ID + */ + async getIntegration(integrationId: number): Promise { + return await fetchAPI(`/v1/integration/integrations/${integrationId}/`); + }, + + /** + * Create new integration + */ + async createIntegration(data: CreateIntegrationData): Promise { + return await fetchAPI('/v1/integration/integrations/', { + method: 'POST', + body: JSON.stringify(data), + }); + }, + + /** + * Update integration + */ + async updateIntegration( + integrationId: number, + data: Partial + ): Promise { + return await fetchAPI(`/v1/integration/integrations/${integrationId}/`, { + method: 'PATCH', + body: JSON.stringify(data), + }); + }, + + /** + * Delete integration + */ + async deleteIntegration(integrationId: number): Promise { + await fetchAPI(`/v1/integration/integrations/${integrationId}/`, { + method: 'DELETE', + }); + }, + + /** + * Test integration connection + */ + async testIntegration(integrationId: number): Promise<{ success: boolean; message: string }> { + return await fetchAPI(`/v1/integration/integrations/${integrationId}/test/`, { + method: 'POST', + }); + }, + + /** + * Sync content from integration + */ + async syncIntegration( + integrationId: number, + syncType: 'full' | 'incremental' = 'incremental' + ): Promise<{ success: boolean; message: string; synced_count?: number }> { + return await fetchAPI(`/v1/integration/integrations/${integrationId}/sync/`, { + method: 'POST', + body: JSON.stringify({ sync_type: syncType }), + }); + }, + + /** + * Get WordPress integration for a site + */ + async getWordPressIntegration(siteId: number): Promise { + const integrations = await this.getSiteIntegrations(siteId); + return integrations.find((i) => i.platform === 'wordpress') || null; + }, + + /** + * Create or update WordPress integration + */ + async saveWordPressIntegration( + siteId: number, + data: { + url: string; + username: string; + app_password: string; + is_active?: boolean; + sync_enabled?: boolean; + } + ): Promise { + const existing = await this.getWordPressIntegration(siteId); + + const integrationData: CreateIntegrationData = { + site: siteId, + platform: 'wordpress', + platform_type: 'cms', + config_json: { + url: data.url, + username: data.username, + }, + credentials: { + app_password: data.app_password, + }, + is_active: data.is_active ?? true, + sync_enabled: data.sync_enabled ?? true, + }; + + if (existing) { + return await this.updateIntegration(existing.id, integrationData); + } else { + return await this.createIntegration(integrationData); + } + }, +}; +