/** * TenantDetail Component * * Read-only detailed view of a single tenant and its integration keys. * Keys are immutable — display and copy only, no regeneration. * * @component * @layer Presentation */ import { useState, useEffect } from 'react'; import { Copy, CheckCircle, Shield, FlaskConical, Megaphone } from 'lucide-react'; import { Link } from '@tanstack/react-router'; import { Modal } from '@/components/shared'; import { Tenant, TenantStatus } from '@/domain/entities/Tenant'; import { IntegrationKey, KeyEnvironment } from '@/domain/entities/IntegrationKey'; import { useIntegrationKeys } from '@/features/integration-keys/hooks'; import { useTranslation } from '@/i18n/TranslationProvider'; import { isWordpressEmbed, wpGetSettings, wpSaveDockPosition } from '@/lib/wp-integration'; interface TenantDetailProps { tenant: Tenant; onClose: () => void; onUpdate?: () => void; } /** WP plugin exposes 3 positions (same set as Shopify app). */ const WP_DOCK_OPTIONS = [ { value: 'top-right', labelKey: 'topRight' as const }, { value: 'middle-right', labelKey: 'middleRight' as const }, { value: 'bottom-right', labelKey: 'bottomRight' as const }, ]; /** * Single Key Block Component (read-only) */ function KeyBlock({ label, description, icon, integrationKey, colorClass, }: { label: string; description: string; icon: React.ReactNode; integrationKey: IntegrationKey | null; colorClass: { bg: string; border: string; text: string; badge: string }; }) { const { t } = useTranslation(); const [copied, setCopied] = useState(false); const displayValue = integrationKey?.key || t('tenants.detail.notGenerated'); const canCopy = !!integrationKey; const handleCopy = async () => { if (!canCopy) return; await navigator.clipboard.writeText(integrationKey!.key); setCopied(true); setTimeout(() => setCopied(false), 2000); }; return (
{icon} {label}
{integrationKey?.isActive() && ( {t('common.active')} )}
{displayValue} {canCopy && ( )}

{description}

{integrationKey?.environment === KeyEnvironment.TESTING && (
{t('tenants.detail.usage', { used: integrationKey.usageCount })}
)}
); } /** * WP Position Picker — embedded dashboard only. * Replaces both the integration-key cards and the snippet constructor when * running inside the WP plugin admin. The WordPress plugin handles key * distribution and snippet injection automatically; the only thing the * merchant still needs to choose is where the widget sits on the page. * Layout + options mirror the Shopify app Appearance page. */ function WpPositionPicker() { const { t } = useTranslation(); const [position, setPosition] = useState('bottom-right'); const [saving, setSaving] = useState(false); const [loaded, setLoaded] = useState(false); useEffect(() => { let cancelled = false; wpGetSettings().then((settings) => { if (cancelled) return; const current = settings?.twwim_dock_position; if (current && WP_DOCK_OPTIONS.some((o) => o.value === current)) { setPosition(current); } setLoaded(true); }); return () => { cancelled = true; }; }, []); const handlePick = async (next: string) => { if (saving || next === position) return; const previous = position; setPosition(next); setSaving(true); const ok = await wpSaveDockPosition(next); setSaving(false); if (!ok) setPosition(previous); }; return (

{t('tenants.detail.widgetPosition')}

{t('tenants.detail.positionDesc')}

{WP_DOCK_OPTIONS.map((opt) => { const active = position === opt.value; return ( ); })}
); } /** * TenantDetail Component */ export function TenantDetail({ tenant, onClose }: TenantDetailProps) { const { t } = useTranslation(); const { keys: integrationKeys } = useIntegrationKeys({ tenantId: tenant.id }); const wpEmbed = isWordpressEmbed(); // Find keys by environment const productionKey = integrationKeys.find( (k) => k.environment === KeyEnvironment.PRODUCTION && k.isActive() ) || null; const testingKey = integrationKeys.find( (k) => k.environment === KeyEnvironment.TESTING && k.isActive() ) || null; return ( {t('common.close')} } >
{/* Tenant Info */}
{tenant.status}
{tenant.domain}
{wpEmbed ? ( ) : ( <> {/* Integration Keys Section */}

{t('tenants.detail.integrationKeys')}

{t('tenants.detail.keysDescription')}

} integrationKey={productionKey} colorClass={{ bg: 'bg-blue-50/50', border: 'border-blue-200', text: 'text-blue-800', badge: 'bg-blue-100 text-blue-800', }} /> } integrationKey={testingKey} colorClass={{ bg: 'bg-purple-50/50', border: 'border-purple-200', text: 'text-purple-800', badge: 'bg-purple-100 text-purple-800', }} />
{/* Plugin settings — per-tenant widget configuration link. The snippet builder (positioning + generated