/** * WooCommerceSyncTab — inline WooCommerce product-sync controls. * * Mounted inside the Knowledge page tab bar (TenantPage.tsx) when running * in WP embed mode and WooCommerce is active on the current WP install. * Replaces the standalone WooCommerceSyncModal that used to live in the * Tenants list. Only loaded in the WP build (VITE_WP_EMBED=true) via * dynamic import — never bundled into the standalone CP dashboard. * * (c) 2026 TWWIM UG. All rights reserved. (www.twwim.com) */ import { useEffect, useState } from 'react'; import { AlertTriangle, Loader2, RefreshCcw, ShoppingBag, CheckCircle2 } from 'lucide-react'; import type { WordpressSyncStatusResponse } from '@archer/api-interface'; import { wordpressApi } from '@/infrastructure/http/api/wordpress'; import { isWooCommerceActive } from '@/lib/wp-integration'; interface WooCommerceSyncTabProps { tenantId: string; tenantDomain: string; } const POLL_INTERVAL_MS = 2_000; const POLL_ACTIVE_STATES: ReadonlyArray = [ 'queued', 'running', ]; export default function WooCommerceSyncTab({ tenantId, tenantDomain }: WooCommerceSyncTabProps) { const [status, setStatus] = useState(null); const [error, setError] = useState(null); const [triggering, setTriggering] = useState(false); useEffect(() => { let cancelled = false; let timer: ReturnType | undefined; const poll = async () => { try { const next = await wordpressApi.getStatus(tenantId); if (cancelled) return; setStatus(next); setError(null); if (next.jobStatus && POLL_ACTIVE_STATES.includes(next.jobStatus)) { timer = setTimeout(poll, POLL_INTERVAL_MS); } } catch (err) { if (cancelled) return; setError((err as Error).message); } }; void poll(); return () => { cancelled = true; if (timer) clearTimeout(timer); }; }, [tenantId]); if (!isWooCommerceActive()) { return (

WooCommerce not active

Product sync requires WooCommerce. Install and activate WooCommerce to enable product knowledge sync.

); } const handleSync = async () => { setTriggering(true); setError(null); try { await wordpressApi.triggerSync(tenantId); const next = await wordpressApi.getStatus(tenantId); setStatus(next); } catch (err) { setError((err as Error).message); } finally { setTriggering(false); } }; const count = status?.count ?? 0; const lastSyncedAt = status?.lastSyncedAt ? new Date(status.lastSyncedAt).toLocaleString() : '—'; const jobStatus = status?.jobStatus ?? null; const isBusy = triggering || (jobStatus !== null && POLL_ACTIVE_STATES.includes(jobStatus)); const isIdle = jobStatus === null || jobStatus === 'idle' || jobStatus === 'completed'; return (
{/* Header — site domain + sync trigger */}

Site: {tenantDomain}

{/* Stats */}
Products synced
{count}
Last sync
{lastSyncedAt}
Status
{isIdle && count > 0 && } {isBusy && } {jobStatus ?? '—'}
{/* Progress bar */} {status?.progress && (
{status.progress.phase} {status.progress.processed} / {status.progress.total}
)} {/* Error */} {status?.lastError && (
Last sync failed ({status.lastError.code}): {status.lastError.message}
)} {error && !status?.lastError && (
{error}
)}
); } function progressPercent(processed: number, total: number): number { if (!total || total <= 0) return 0; return Math.min(100, Math.round((processed / total) * 100)); }