This commit is contained in:
IGNY8 VPS (Salman)
2026-01-13 01:52:40 +00:00
parent 47a5a8b1da
commit 5ce0d02636
3 changed files with 34 additions and 90 deletions

View File

@@ -44,16 +44,12 @@ class CreditUsageLogSerializer(serializers.ModelSerializer):
read_only_fields = ['created_at', 'account'] read_only_fields = ['created_at', 'account']
def get_client_cost(self, obj) -> str: def get_client_cost(self, obj) -> str:
"""Calculate client-facing cost from credits * default_credit_price_usd""" """Calculate client-facing cost from credits * default_credit_price_usd (price per credit)"""
from igny8_core.business.billing.models import BillingConfiguration from igny8_core.business.billing.models import BillingConfiguration
try: config = BillingConfiguration.get_config()
config = BillingConfiguration.get_config() price_per_credit = config.default_credit_price_usd
price_per_credit = config.default_credit_price_usd client_cost = Decimal(obj.credits_used) * price_per_credit
client_cost = Decimal(obj.credits_used) * price_per_credit return str(client_cost.quantize(Decimal('0.0001')))
return str(client_cost.quantize(Decimal('0.0001')))
except Exception:
# Fallback to cost_usd if billing config unavailable
return str(obj.cost_usd) if obj.cost_usd else '0.0000'
def get_site_name(self, obj) -> Optional[str]: def get_site_name(self, obj) -> Optional[str]:
"""Get the site name if available""" """Get the site name if available"""

View File

@@ -6,23 +6,17 @@ import React, { useState, useEffect } from 'react';
import { Card } from '../ui/card'; import { Card } from '../ui/card';
import Button from '../ui/button/Button'; import Button from '../ui/button/Button';
import IconButton from '../ui/button/IconButton'; import IconButton from '../ui/button/IconButton';
import Label from '../form/Label';
import Input from '../form/input/InputField'; import Input from '../form/input/InputField';
import Checkbox from '../form/input/Checkbox';
import Switch from '../form/switch/Switch';
import { useToast } from '../ui/toast/ToastContainer'; import { useToast } from '../ui/toast/ToastContainer';
import { fetchAPI, API_BASE_URL } from '../../services/api'; import { fetchAPI, API_BASE_URL } from '../../services/api';
import { import {
CheckCircleIcon,
AlertIcon,
DownloadIcon, DownloadIcon,
PlusIcon, PlusIcon,
CopyIcon, CopyIcon,
TrashBinIcon, TrashBinIcon,
GlobeIcon, GlobeIcon,
KeyIcon,
RefreshCwIcon, RefreshCwIcon,
InfoIcon InfoIcon,
} from '../../icons'; } from '../../icons';
interface WordPressIntegrationFormProps { interface WordPressIntegrationFormProps {
@@ -283,56 +277,40 @@ export default function WordPressIntegrationForm({
{/* Connection Status & Test Button */} {/* Connection Status & Test Button */}
{apiKey && ( {apiKey && (
<div className="flex items-center gap-3"> <div className="flex flex-col items-end gap-2">
{/* Status Indicator - Uses theme colors from design-system */} {/* Connection Status Indicator */}
<div className={`flex items-center gap-2 px-3 py-1.5 rounded-lg border ${ <div className="flex items-center gap-3 px-4 py-2 rounded-lg bg-gray-50 dark:bg-gray-800">
connectionStatus === 'connected' <span
? 'bg-success-50 dark:bg-success-900/20 border-success-200 dark:border-success-800' className={`inline-block w-4 h-4 rounded-full ${
: connectionStatus === 'testing' connectionStatus === 'connected' ? 'bg-success-500' :
? 'bg-brand-50 dark:bg-brand-900/20 border-brand-200 dark:border-brand-800' connectionStatus === 'testing' ? 'bg-brand-500 animate-pulse' :
: connectionStatus === 'api_key_pending' connectionStatus === 'api_key_pending' ? 'bg-warning-500' :
? 'bg-warning-50 dark:bg-warning-900/20 border-warning-200 dark:border-warning-800' connectionStatus === 'plugin_missing' ? 'bg-warning-500' :
: connectionStatus === 'plugin_missing' connectionStatus === 'error' ? 'bg-error-500' :
? 'bg-warning-50 dark:bg-warning-900/20 border-warning-200 dark:border-warning-800' 'bg-brand-500'
: connectionStatus === 'error' }`}
? 'bg-error-50 dark:bg-error-900/20 border-error-200 dark:border-error-800' />
: 'bg-gray-50 dark:bg-gray-800/50 border-gray-200 dark:border-gray-700' <span className="text-sm font-medium text-gray-700 dark:text-gray-200">
}`}> {connectionStatus === 'connected' && 'Connected'}
{connectionStatus === 'connected' && ( {connectionStatus === 'testing' && 'Testing...'}
<><CheckCircleIcon className="w-4 h-4 text-success-600 dark:text-success-400" /> {connectionStatus === 'api_key_pending' && 'Pending Setup'}
<span className="text-sm font-medium text-success-700 dark:text-success-300">Connected</span></> {connectionStatus === 'plugin_missing' && 'Plugin Missing'}
)} {connectionStatus === 'error' && 'Error'}
{connectionStatus === 'testing' && ( {connectionStatus === 'unknown' && 'Configured'}
<><RefreshCwIcon className="w-4 h-4 text-brand-600 dark:text-brand-400 animate-spin" /> </span>
<span className="text-sm font-medium text-brand-700 dark:text-brand-300">Testing...</span></>
)}
{connectionStatus === 'api_key_pending' && (
<><AlertIcon className="w-4 h-4 text-warning-600 dark:text-warning-400" />
<span className="text-sm font-medium text-warning-700 dark:text-warning-300">Pending Setup</span></>
)}
{connectionStatus === 'plugin_missing' && (
<><AlertIcon className="w-4 h-4 text-warning-600 dark:text-warning-400" />
<span className="text-sm font-medium text-warning-700 dark:text-warning-300">Plugin Missing</span></>
)}
{connectionStatus === 'error' && (
<><AlertIcon className="w-4 h-4 text-error-600 dark:text-error-400" />
<span className="text-sm font-medium text-error-700 dark:text-error-300">Error</span></>
)}
{connectionStatus === 'unknown' && (
<><InfoIcon className="w-4 h-4 text-gray-500 dark:text-gray-400" />
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">Not Tested</span></>
)}
</div> </div>
{/* Test Connection Button - IconButton only */} {/* Test Connection Button */}
<IconButton <Button
onClick={testConnection} onClick={testConnection}
variant="outline" variant="outline"
tone="brand" tone="brand"
size="sm"
disabled={testingConnection || !apiKey} disabled={testingConnection || !apiKey}
title="Test Connection" startIcon={<RefreshCwIcon className={`w-4 h-4 ${testingConnection ? 'animate-spin' : ''}`} />}
icon={<RefreshCwIcon className={`w-4 h-4 ${testingConnection ? 'animate-spin' : ''}`} />} >
/> Test Connection
</Button>
</div> </div>
)} )}
</div> </div>

View File

@@ -499,19 +499,6 @@ export default function SiteSettings() {
return `${months}mo ago`; return `${months}mo ago`;
}; };
// Integration status - tracks actual connection state
const [integrationStatus, setIntegrationStatus] = useState<'connected' | 'configured' | 'not_configured'>('not_configured');
// Check integration status based on API key presence (will be updated by WordPressIntegrationForm)
useEffect(() => {
if (site?.wp_api_key) {
// API key exists - mark as configured (actual connection tested in WordPressIntegrationForm)
setIntegrationStatus('configured');
} else {
setIntegrationStatus('not_configured');
}
}, [site?.wp_api_key]);
// Sync Now handler - tests actual WordPress connection // Sync Now handler - tests actual WordPress connection
const [syncLoading, setSyncLoading] = useState(false); const [syncLoading, setSyncLoading] = useState(false);
const [lastSyncTime, setLastSyncTime] = useState<string | null>(null); const [lastSyncTime, setLastSyncTime] = useState<string | null>(null);
@@ -535,10 +522,8 @@ export default function SiteSettings() {
const healthChecks = res.health_checks || {}; const healthChecks = res.health_checks || {};
if (healthChecks.plugin_has_api_key) { if (healthChecks.plugin_has_api_key) {
setIntegrationStatus('connected');
toast.success('WordPress connection verified - fully connected!'); toast.success('WordPress connection verified - fully connected!');
} else if (healthChecks.plugin_installed) { } else if (healthChecks.plugin_installed) {
setIntegrationStatus('configured');
toast.warning('Plugin found but API key not configured in WordPress'); toast.warning('Plugin found but API key not configured in WordPress');
} else { } else {
toast.warning('WordPress reachable but IGNY8 plugin not installed'); toast.warning('WordPress reachable but IGNY8 plugin not installed');
@@ -684,21 +669,6 @@ export default function SiteSettings() {
Publishing Publishing
</Button> </Button>
</div> </div>
{/* Integration Status Indicator - Larger */}
<div className="flex items-center gap-3 px-4 py-2 rounded-lg bg-gray-50 dark:bg-gray-800 flex-shrink-0">
<span
className={`inline-block w-3 h-3 rounded-full ${
integrationStatus === 'connected' ? 'bg-success-500' :
integrationStatus === 'configured' ? 'bg-brand-500' : 'bg-gray-300'
}`}
/>
<span className="text-sm font-medium text-gray-700 dark:text-gray-200">
{integrationStatus === 'connected' && 'Connected'}
{integrationStatus === 'configured' && 'Configured'}
{integrationStatus === 'not_configured' && 'Not Configured'}
</span>
</div>
</div> </div>
</div> </div>