Skip to main content

Integration Components

The Integration Components provide comprehensive third-party service integration functionality for the WorkPayCore Frontend application. These components handle external service connections, authentication flows, integration management, and service configuration with consistent UI patterns.

Overview​

This document covers integration-related components that enable connections to external services like QuickBooks, Xero, Sage, WaveApps, and other third-party platforms with OAuth authentication, status management, and configuration interfaces.

Components Overview​

Core Integration Components​

Integration Management Components​

  • IntegrationsHeader - Header component for integration pages
  • CustomizedIntegratedApps - Hook for managing integration state

Integration Patterns​

  • OAuth Authentication Flow - OAuth service connection patterns
  • Service Status Management - Integration status tracking
  • Multi-Service Integration - Managing multiple service connections

AccountsCard​

Service integration card component that displays connection status, service information, and connection controls.

Component Location​

import AccountsCard from 'components/Integrations/AccountsCard';

Props​

PropTypeRequiredDefaultDescription
softwareDetailsobject✓-Integration service details

TypeScript Interface​

interface SoftwareDetails {
id: number;
title: string;
icon: ReactElement;
status: 'Connected' | 'Disconnected';
description: string;
to?: string;
view: boolean;
application_code?: string;
oauth_client_id?: string;
oauth_client_secret?: string;
}

interface AccountsCardProps {
softwareDetails: SoftwareDetails;
}

Features​

  • Service Information Display: Shows service name, icon, and description
  • Connection Status: Visual indicator of connection state
  • Connection Toggle: Switch to connect/disconnect services
  • Permission-Based Access: Respects user permissions for integration management
  • Modal Integration: Triggers connection status modals

Usage Examples​

Basic Integration Card​

import AccountsCard from 'components/Integrations/AccountsCard';

function IntegrationGrid() {
const accountingSoftware = [
{
id: 1,
title: 'QuickBooks',
icon: <QuickBooksIcon height='40px' width='40px' />,
status: 'Disconnected',
description:
'Integrate with QuickBooks for seamless accounting data sync.',
to: '/settings/account_integrations/quick_books',
view: true,
},
{
id: 2,
title: 'Xero',
icon: <XeroIcon height='40px' width='40px' />,
status: 'Connected',
description: 'Sync payroll data with Xero accounting platform.',
to: '/settings/account_integrations/xero',
view: true,
},
];

return (
<Grid templateColumns='repeat(2, 1fr)' gap={6}>
{accountingSoftware.map(software => (
<AccountsCard key={software.id} softwareDetails={software} />
))}
</Grid>
);
}

Integration with Custom Status​

function CustomIntegrationCard() {
const [integrationData, setIntegrationData] = useState({
id: 1,
title: 'Custom ERP',
icon: <CustomIcon />,
status: 'Disconnected',
description: 'Connect to your custom ERP system for data synchronization.',
view: true,
custom_config: {
api_endpoint: 'https://api.custom-erp.com',
api_key: process.env.REACT_APP_CUSTOM_ERP_KEY,
},
});

const handleConnectionToggle = async isConnected => {
setIntegrationData(prev => ({
...prev,
status: isConnected ? 'Connected' : 'Disconnected',
}));

// Handle custom connection logic
if (isConnected) {
await connectToCustomERP(integrationData.custom_config);
} else {
await disconnectFromCustomERP();
}
};

return (
<VStack spacing={4}>
<AccountsCard softwareDetails={integrationData} />

{integrationData.status === 'Connected' && (
<Alert status='success'>
<AlertIcon />
&lt;AlertDescription&gt;
Successfully connected to {integrationData.title}
</AlertDescription>
</Alert>
)}
</VStack>
);
}

ConnectionStatusModal​

Modal component for managing service connection and disconnection flows with OAuth authentication.

Component Location​

import ConnectionStatusModal from 'components/Integrations/ConnectionStatusModal';

Props​

PropTypeRequiredDefaultDescription
isOpenboolean✓-Modal open state
onClosefunction✓-Modal close handler
dataobject✓-Integration service data

Features​

  • OAuth Integration: Handles OAuth authentication flows
  • Connect/Disconnect Actions: Manages service connection state
  • External Window Handling: Opens OAuth flows in new windows
  • Token Management: Includes authentication tokens in OAuth URLs
  • Error Handling: Manages connection errors and failures

Usage Examples​

Basic Connection Modal​

import ConnectionStatusModal from 'components/Integrations/ConnectionStatusModal';
import { useDisclosure } from '@chakra-ui/react';

function ServiceConnectionManager() {
const { isOpen, onOpen, onClose } = useDisclosure();
const [selectedService, setSelectedService] = useState(null);

const handleConnectService = serviceData => {
setSelectedService(serviceData);
onOpen();
};

const serviceData = {
id: 1,
title: 'QuickBooks',
application_code: 'QUICKBOOKS',
status: 'Disconnected',
};

return (
<>
<Button onClick={() => handleConnectService(serviceData)}>
Connect to QuickBooks
</Button>

<ConnectionStatusModal
isOpen={isOpen}
onClose={onClose}
data={selectedService}
/>
</>
);
}

Advanced Connection Flow​

function AdvancedConnectionFlow() {
const [connectionState, setConnectionState] = useState('idle');
const [connectionError, setConnectionError] = useState(null);

const handleConnectionStart = () => {
setConnectionState('connecting');
setConnectionError(null);
};

const handleConnectionSuccess = () => {
setConnectionState('connected');
// Refresh integration data
queryClient.invalidateQueries(['integrations']);
};

const handleConnectionError = error => {
setConnectionState('error');
setConnectionError(error.message);
};

return (
<VStack spacing={4}>
{connectionState === 'connecting' && (
<Alert status='info'>
<Spinner size='sm' mr={2} />
&lt;AlertDescription&gt;
Connecting to service... You may be redirected to complete
authentication.
</AlertDescription>
</Alert>
)}

{connectionState === 'error' && (
<Alert status='error'>
<AlertIcon />
&lt;AlertDescription&gt;
Connection failed: {connectionError}
</AlertDescription>
</Alert>
)}

<ConnectionStatusModal
isOpen={isOpen}
onClose={onClose}
data={selectedService}
onConnectionStart={handleConnectionStart}
onConnectionSuccess={handleConnectionSuccess}
onConnectionError={handleConnectionError}
/>
</VStack>
);
}

AuthorizeCard​

Authorization interface component for handling service reauthorization and OAuth flows.

Component Location​

import AuthorizeCard from 'components/Integrations/AuthorizeCard';

Props​

PropTypeRequiredDefaultDescription
actionstring✓-Action type ('view', 'connect')
isOpenboolean✓-Modal open state
onClosefunction✓-Modal close handler

Features​

  • Reauthorization Flow: Handles expired token reauthorization
  • Service Selection: Allows selection of services for authorization
  • OAuth URL Generation: Creates proper OAuth authorization URLs
  • Token Injection: Adds authentication tokens to OAuth flows

Usage Examples​

Service Reauthorization​

import AuthorizeCard from 'components/Integrations/AuthorizeCard';

function ServiceReauthorization() {
const { isOpen, onOpen, onClose } = useDisclosure();
const [needsReauth, setNeedsReauth] = useState(false);

// Check if service needs reauthorization
useEffect(() => {
checkAuthorizationStatus().then(status => {
if (status.requiresReauth) {
setNeedsReauth(true);
onOpen();
}
});
}, []);

return (
<>
{needsReauth && (
<Alert status='warning' mb={4}>
<AlertIcon />
&lt;AlertDescription&gt;
Your QuickBooks integration needs to be reauthorized.
<Button size='sm' ml={2} onClick={onOpen}>
Reauthorize Now
</Button>
</AlertDescription>
</Alert>
)}

<AuthorizeCard action='view' isOpen={isOpen} onClose={onClose} />
</>
);
}

SettingsIntegrationPageWrapper​

Page wrapper component that provides consistent layout for integration settings pages.

Component Location​

import SettingsIntegrationPageWrapper from 'containers/Settings/common/SettingsIntegrationPageWrapper';

Props​

PropTypeRequiredDefaultDescription
dataobject✓-Page data and configuration
tabDataarray✓-Tab configuration data
handleSubmitfunction✓-Form submission handler
noDataMsgstring--No data message text
isLoadingboolean-falseLoading state

Features​

  • Consistent Layout: Standardized integration page structure
  • Tab Navigation: Vertical tab navigation for integration sections
  • Header Integration: Includes integration-specific headers
  • Empty State Handling: Shows appropriate messages when no data exists
  • Loading States: Manages loading indicators
  • Reauthorization Detection: Automatically detects and handles reauth needs

Usage Examples​

Integration Settings Page​

import SettingsIntegrationPageWrapper from 'containers/Settings/common/SettingsIntegrationPageWrapper';

function QuickBooksSettingsPage() {
const [isLoading, setIsLoading] = useState(true);
const [tabData, setTabData] = useState([]);

const pageData = {
pageTitle: 'QuickBooks Integration',
description:
'Manage your QuickBooks integration settings and data sync options.',
icon: <QuickBooksIcon />,
};

const integrationTabs = [
{
id: 1,
title: 'Sync Settings',
children: <SyncSettingsPanel />,
},
{
id: 2,
title: 'Field Mapping',
children: <FieldMappingPanel />,
},
{
id: 3,
title: 'Sync History',
children: <SyncHistoryPanel />,
},
];

useEffect(() => {
loadIntegrationData().then(data => {
setTabData(integrationTabs);
setIsLoading(false);
});
}, []);

const handleSettingsSubmit = settings => {
return updateIntegrationSettings(settings);
};

return (
<SettingsIntegrationPageWrapper
data={pageData}
tabData={integrationTabs}
handleSubmit={handleSettingsSubmit}
isLoading={isLoading}
noDataMsg='No integration settings available.'
/>
);
}

Custom Integration Page​

function CustomIntegrationPage() {
const [configData, setConfigData] = useState(null);
const [isConfiguring, setIsConfiguring] = useState(false);

const pageData = {
pageTitle: 'Custom API Integration',
description: 'Configure custom API endpoints and authentication settings.',
customActions: (
<Button onClick={() => setIsConfiguring(true)}>Add New Endpoint</Button>
),
};

const configurationTabs = useMemo(() => {
if (!configData) return [];

return configData.endpoints.map(endpoint => ({
id: endpoint.id,
title: endpoint.name,
children: (
<EndpointConfigurationPanel
endpoint={endpoint}
onUpdate={handleEndpointUpdate}
/>
),
}));
}, [configData]);

const handleConfigurationSubmit = async config => {
setIsConfiguring(true);
try {
await saveEndpointConfiguration(config);
await refreshConfigData();
} finally {
setIsConfiguring(false);
}
};

return (
<SettingsIntegrationPageWrapper
data={pageData}
tabData={configurationTabs}
handleSubmit={handleConfigurationSubmit}
isLoading={isConfiguring}
noDataMsg='No API endpoints configured. Add your first endpoint to get started.'
/>
);
}

Integration Patterns​

OAuth Authentication Flow Pattern​

function OAuthIntegrationFlow() {
const [authState, setAuthState] = useState('idle');
const [authData, setAuthData] = useState(null);

const initiateOAuth = async serviceCode => {
setAuthState('initializing');

try {
// Get OAuth authorization URL
const response = await httpV2.get('/integrations/auth/url', {
params: { application_code: serviceCode },
});

if (response.data.success) {
setAuthState('authorizing');

// Open OAuth window
const authWindow = window.open(
`${response.data.data.value}&token=${USER_TOKEN}`,
'_blank',
'width=600,height=600',
);

// Monitor for OAuth completion
const checkClosed = setInterval(() => {
if (authWindow.closed) {
clearInterval(checkClosed);
checkAuthorizationResult();
}
}, 1000);
}
} catch (error) {
setAuthState('error');
console.error('OAuth initialization failed:', error);
}
};

const checkAuthorizationResult = async () => {
try {
const status = await checkIntegrationStatus();
if (status.connected) {
setAuthState('connected');
setAuthData(status.data);
} else {
setAuthState('failed');
}
} catch (error) {
setAuthState('error');
}
};

const disconnectService = async () => {
setAuthState('disconnecting');

try {
await httpV2.delete('/integrations/disconnect', {
data: { id: authData.id },
});

setAuthState('disconnected');
setAuthData(null);
} catch (error) {
setAuthState('error');
}
};

return (
<VStack spacing={4}>
<Text fontSize='lg' fontWeight='bold'>
Service Integration Status
</Text>

{authState === 'idle' && (
<Button onClick={() => initiateOAuth('QUICKBOOKS')}>
Connect to QuickBooks
</Button>
)}

{authState === 'initializing' && (
&lt;HStack&gt;
<Spinner size='sm' />
&lt;Text&gt;Initializing connection...</Text>
</HStack>
)}

{authState === 'authorizing' && (
<Alert status='info'>
<AlertIcon />
&lt;AlertDescription&gt;
Please complete authorization in the popup window.
</AlertDescription>
</Alert>
)}

{authState === 'connected' && (
<VStack spacing={2}>
<Alert status='success'>
<AlertIcon />
&lt;AlertDescription&gt;
Successfully connected to QuickBooks!
</AlertDescription>
</Alert>
<Button variant='outline' onClick={disconnectService}>
Disconnect
</Button>
</VStack>
)}

{authState === 'error' && (
<Alert status='error'>
<AlertIcon />
&lt;AlertDescription&gt;
Connection failed. Please try again.
</AlertDescription>
</Alert>
)}
</VStack>
);
}

Service Status Management Pattern​

function ServiceStatusManager() {
const [integrations, setIntegrations] = useState([]);
const [refreshing, setRefreshing] = useState(false);

const { data: integrationsData, isLoading } = useQuery(
['integrations'],
fetchIntegratedApplications,
{
refetchInterval: 30000, // Refresh every 30 seconds
},
);

useEffect(() => {
if (integrationsData) {
const formattedIntegrations = integrationsData.map(integration => ({
...integration,
lastSync: integration.last_sync_at
? new Date(integration.last_sync_at)
: null,
status: determineIntegrationStatus(integration),
}));

setIntegrations(formattedIntegrations);
}
}, [integrationsData]);

const determineIntegrationStatus = integration => {
if (!integration.connected) return 'disconnected';
if (integration.sync_errors > 0) return 'error';
if (integration.requires_reauth) return 'reauth_needed';
return 'connected';
};

const refreshIntegrationStatus = async () => {
setRefreshing(true);
try {
await queryClient.invalidateQueries(['integrations']);
await new Promise(resolve => setTimeout(resolve, 1000)); // Brief delay for UX
} finally {
setRefreshing(false);
}
};

const getStatusColor = status => {
switch (status) {
case 'connected':
return 'green';
case 'disconnected':
return 'gray';
case 'error':
return 'red';
case 'reauth_needed':
return 'orange';
default:
return 'gray';
}
};

const getStatusText = status => {
switch (status) {
case 'connected':
return 'Connected';
case 'disconnected':
return 'Disconnected';
case 'error':
return 'Error';
case 'reauth_needed':
return 'Reauthorization Required';
default:
return 'Unknown';
}
};

return (
<VStack spacing={4} align='stretch'>
<HStack justify='space-between'>
<Text fontSize='lg' fontWeight='bold'>
Integration Status
</Text>
<Button
size='sm'
onClick={refreshIntegrationStatus}
isLoading={refreshing}
leftIcon={<RefreshIcon />}
>
Refresh
</Button>
</HStack>

{isLoading ? (
<SkeletonLoader />
) : (
<SimpleGrid columns={2} spacing={4}>
{integrations.map(integration => (
<Box
key={integration.id}
p={4}
borderWidth='1px'
borderRadius='md'
borderColor={getStatusColor(integration.status)}
>
<HStack justify='space-between' mb={2}>
<Text fontWeight='bold'>{integration.name}</Text>
<Badge colorScheme={getStatusColor(integration.status)}>
{getStatusText(integration.status)}
</Badge>
</HStack>

{integration.lastSync && (
<Text fontSize='sm' color='gray.600'>
Last sync: {formatDistanceToNow(integration.lastSync)} ago
</Text>
)}

{integration.status === 'error' && (
<Text fontSize='sm' color='red.500' mt={1}>
{integration.error_message}
</Text>
)}
</Box>
))}
</SimpleGrid>
)}
</VStack>
);
}

Multi-Service Integration Pattern​

function MultiServiceIntegrationManager() {
const [selectedServices, setSelectedServices] = useState(new Set());
const [bulkActionLoading, setBulkActionLoading] = useState(false);

const availableServices = [
{ code: 'QUICKBOOKS', name: 'QuickBooks', icon: <QuickBooksIcon /> },
{ code: 'XERO', name: 'Xero', icon: <XeroIcon /> },
{ code: 'SAGE', name: 'Sage', icon: <SageIcon /> },
{ code: 'WAVEAPPS', name: 'WaveApps', icon: <WaveAppsIcon /> },
];

const handleServiceSelect = serviceCode => {
setSelectedServices(prev => {
const newSet = new Set(prev);
if (newSet.has(serviceCode)) {
newSet.delete(serviceCode);
} else {
newSet.add(serviceCode);
}
return newSet;
});
};

const handleBulkConnect = async () => {
setBulkActionLoading(true);

try {
const connectionPromises = Array.from(selectedServices).map(serviceCode =>
connectToService(serviceCode),
);

const results = await Promise.allSettled(connectionPromises);

const succeeded = results.filter(r => r.status === 'fulfilled').length;
const failed = results.filter(r => r.status === 'rejected').length;

if (succeeded > 0) {
toast({
title: `Connected to ${succeeded} service(s)`,
status: 'success',
});
}

if (failed > 0) {
toast({
title: `Failed to connect to ${failed} service(s)`,
status: 'error',
});
}

setSelectedServices(new Set());
} finally {
setBulkActionLoading(false);
}
};

const handleBulkDisconnect = async () => {
setBulkActionLoading(true);

try {
const disconnectionPromises = Array.from(selectedServices).map(
serviceCode => disconnectFromService(serviceCode),
);

await Promise.all(disconnectionPromises);

toast({
title: `Disconnected from ${selectedServices.size} service(s)`,
status: 'success',
});

setSelectedServices(new Set());
} finally {
setBulkActionLoading(false);
}
};

return (
<VStack spacing={6} align='stretch'>
<HStack justify='space-between'>
<Text fontSize='xl' fontWeight='bold'>
Service Integrations
</Text>

{selectedServices.size > 0 && (
<HStack spacing={2}>
<Button
size='sm'
onClick={handleBulkConnect}
isLoading={bulkActionLoading}
colorScheme='green'
>
Connect Selected ({selectedServices.size})
</Button>
<Button
size='sm'
variant='outline'
onClick={handleBulkDisconnect}
isLoading={bulkActionLoading}
>
Disconnect Selected
</Button>
</HStack>
)}
</HStack>

<SimpleGrid columns={2} spacing={4}>
{availableServices.map(service => (
<Box
key={service.code}
p={4}
borderWidth='2px'
borderRadius='md'
borderColor={
selectedServices.has(service.code) ? 'blue.300' : 'gray.200'
}
cursor='pointer'
onClick={() => handleServiceSelect(service.code)}
transition='all 0.2s'
_hover={{ borderColor: 'blue.300' }}
>
<VStack spacing={3}>
<Checkbox
isChecked={selectedServices.has(service.code)}
onChange={() => handleServiceSelect(service.code)}
onClick={e => e.stopPropagation()}
/>

{service.icon}

<Text fontWeight='bold'>{service.name}</Text>

<IntegrationStatusBadge serviceCode={service.code} />
</VStack>
</Box>
))}
</SimpleGrid>
</VStack>
);
}

Best Practices​

Integration Security​

  1. Token Management

    • Store OAuth tokens securely
    • Implement token refresh mechanisms
    • Use HTTPS for all OAuth flows
    • Validate redirect URIs
  2. Error Handling

    • Graceful degradation for failed connections
    • Clear error messages for users
    • Retry mechanisms for transient failures
    • Logging for debugging
  3. Data Synchronization

    • Implement conflict resolution strategies
    • Handle partial sync failures
    • Provide sync status visibility
    • Rate limiting for API calls

User Experience​

  1. Clear Status Communication

    • Visual indicators for connection states
    • Progress feedback during operations
    • Clear error messages with remediation steps
    • Success confirmations
  2. Permission Management

    • Role-based access to integration features
    • Clear permission requirements
    • Graceful handling of insufficient permissions
  3. Performance Optimization

    • Lazy loading of integration data
    • Caching of connection statuses
    • Background sync operations
    • Minimal UI blocking

Testing​

Unit Tests​

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import AccountsCard from 'components/Integrations/AccountsCard';

describe('AccountsCard', () => {
const mockSoftware = {
id: 1,
title: 'QuickBooks',
icon: <div>QB Icon</div>,
status: 'Disconnected',
description: 'QuickBooks integration',
view: true,
};

it('renders software information correctly', () => {
render(<AccountsCard softwareDetails={mockSoftware} />);

expect(screen.getByText('QuickBooks')).toBeInTheDocument();
expect(screen.getByText('QuickBooks integration')).toBeInTheDocument();
expect(screen.getByText('Disconnected')).toBeInTheDocument();
});

it('shows connection toggle for authorized users', () => {
render(<AccountsCard softwareDetails={mockSoftware} />);

const toggleSwitch = screen.getByRole('switch');
expect(toggleSwitch).toBeInTheDocument();
expect(toggleSwitch).not.toBeChecked();
});

it('opens connection modal when toggle is clicked', async () => {
const { user } = render(<AccountsCard softwareDetails={mockSoftware} />);

const toggleSwitch = screen.getByRole('switch');
await user.click(toggleSwitch);

await waitFor(() => {
expect(screen.getByText('Connection Settings')).toBeInTheDocument();
});
});
});

Integration Tests​

describe('Integration Flow', () => {
it('completes OAuth connection flow', async () => {
const mockOAuthResponse = {
success: true,
data: { value: 'https://oauth.service.com/auth?token=abc' },
};

jest.spyOn(httpV2, 'get').mockResolvedValue({ data: mockOAuthResponse });

const { user } = render(<IntegrationPage />);

// Click connect button
await user.click(screen.getByText('Connect to QuickBooks'));

// Verify OAuth URL request
expect(httpV2.get).toHaveBeenCalledWith('/integrations/auth/url', {
params: { application_code: 'QUICKBOOKS' },
});

// Verify window.open was called
expect(window.open).toHaveBeenCalledWith(
expect.stringContaining('oauth.service.com'),
'_blank',
);
});

it('handles connection errors gracefully', async () => {
jest.spyOn(httpV2, 'get').mockRejectedValue(new Error('Network error'));

const { user } = render(<IntegrationPage />);

await user.click(screen.getByText('Connect to QuickBooks'));

await waitFor(() => {
expect(screen.getByText(/connection failed/i)).toBeInTheDocument();
});
});
});

Migration Guide​

From Legacy Integration Components​

  1. Update Import Paths

    // Old
    import IntegrationCard from 'components/OldIntegration';

    // New
    import AccountsCard from 'components/Integrations/AccountsCard';
  2. Update Props Structure

    // Old
    <IntegrationCard
    name="QuickBooks"
    connected={false}
    onToggle={handleToggle}
    />

    // New
    <AccountsCard
    softwareDetails={{
    title: "QuickBooks",
    status: "Disconnected",
    // ... other properties
    }}
    />
  3. Update Event Handling

    // Old
    const handleToggle = isConnected => {
    // Handle connection state
    };

    // New
    // Connection handling is managed internally
    // Use status updates and modal callbacks

Advanced Usage​

Custom OAuth Provider​

function CustomOAuthProvider({ children, onTokenUpdate }) {
const [authTokens, setAuthTokens] = useState({});

const registerOAuthService = useCallback((serviceCode, config) => {
// Register custom OAuth configuration
oauthConfigs[serviceCode] = config;
}, []);

const initiateOAuth = useCallback(async serviceCode => {
const config = oauthConfigs[serviceCode];
if (!config) {
throw new Error(`OAuth config not found for ${serviceCode}`);
}

const authUrl = buildOAuthUrl(config);
const authWindow = window.open(authUrl, '_blank');

return new Promise((resolve, reject) => {
const checkWindow = setInterval(() => {
try {
if (authWindow.closed) {
clearInterval(checkWindow);
resolve(extractTokenFromCallback());
}
} catch (error) {
clearInterval(checkWindow);
reject(error);
}
}, 1000);
});
}, []);

const context = {
authTokens,
registerOAuthService,
initiateOAuth,
};

return (
<OAuthContext.Provider value={context}>{children}</OAuthContext.Provider>
);
}

Integration Monitoring Dashboard​

function IntegrationMonitoringDashboard() {
const [metrics, setMetrics] = useState({});
const [alerts, setAlerts] = useState([]);

useEffect(() => {
const interval = setInterval(async () => {
const [metricsData, alertsData] = await Promise.all([
fetchIntegrationMetrics(),
fetchIntegrationAlerts(),
]);

setMetrics(metricsData);
setAlerts(alertsData);
}, 30000);

return () => clearInterval(interval);
}, []);

return (
<Grid templateColumns='repeat(3, 1fr)' gap={6}>
<GridItem colSpan={2}>
<IntegrationMetricsChart data={metrics} />
</GridItem>

&lt;GridItem&gt;
<IntegrationAlertsPanel alerts={alerts} />
</GridItem>

<GridItem colSpan={3}>
<IntegrationHealthStatus integrations={metrics.integrations} />
</GridItem>
</Grid>
);
}