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>
);
}