|
1 | 1 | 'use client' |
2 | 2 |
|
3 | | -import { useEffect, useMemo, useRef, useState } from 'react' |
| 3 | +import { type ReactNode, useEffect, useMemo, useRef, useState } from 'react' |
4 | 4 | import { Chip, ChipDropdown, ChipLink, cn } from '@sim/emcn' |
5 | 5 | import { ArrowLeft, Plus } from '@sim/emcn/icons' |
6 | 6 | import { useRouter } from 'next/navigation' |
@@ -67,44 +67,82 @@ interface IntegrationBlockDetailProps { |
67 | 67 | /** |
68 | 68 | * Integration detail page, shown only when the viewer's integration policy |
69 | 69 | * permits the integration. A direct link to a restricted one renders an |
70 | | - * access-required state instead of the connect actions. |
| 70 | + * access-required state instead of the connect actions, and the check fails |
| 71 | + * closed until the deployment allowlist has loaded. |
71 | 72 | */ |
72 | 73 | export function IntegrationBlockDetail({ integration, workspaceId }: IntegrationBlockDetailProps) { |
73 | | - const { isLoading, isBlockPermitted, isBlockRequestable } = usePermissionConfig() |
| 74 | + const { |
| 75 | + isBlockPermitted, |
| 76 | + isBlockRequestable, |
| 77 | + isIntegrationAvailabilityReady, |
| 78 | + integrationAvailabilityError, |
| 79 | + refetchIntegrationAvailability, |
| 80 | + } = usePermissionConfig() |
74 | 81 | const accessRequests = useWorkspaceAccessRequestFeatures() |
75 | 82 |
|
76 | | - if (isLoading) return <IntegrationBlockDetailFallback workspaceId={workspaceId} /> |
| 83 | + if (integrationAvailabilityError) { |
| 84 | + return ( |
| 85 | + <IntegrationAccessNotice |
| 86 | + workspaceId={workspaceId} |
| 87 | + title='Unable to check access' |
| 88 | + description={integrationAvailabilityError.message} |
| 89 | + action={<Chip onClick={() => void refetchIntegrationAvailability()}>Try again</Chip>} |
| 90 | + /> |
| 91 | + ) |
| 92 | + } |
| 93 | + if (!isIntegrationAvailabilityReady) { |
| 94 | + return <IntegrationBlockDetailFallback workspaceId={workspaceId} /> |
| 95 | + } |
77 | 96 | if (isBlockPermitted(integration.type)) { |
78 | 97 | return <IntegrationBlockDetailContent integration={integration} workspaceId={workspaceId} /> |
79 | 98 | } |
80 | 99 |
|
81 | 100 | const requestable = accessRequests.data?.enabled === true && isBlockRequestable(integration.type) |
82 | 101 |
|
| 102 | + return ( |
| 103 | + <IntegrationAccessNotice |
| 104 | + workspaceId={workspaceId} |
| 105 | + title='Access required' |
| 106 | + description={ |
| 107 | + requestable |
| 108 | + ? 'Ask an administrator for access.' |
| 109 | + : 'Your organization restricts this integration.' |
| 110 | + } |
| 111 | + action={ |
| 112 | + requestable ? ( |
| 113 | + <RequestAccessAction |
| 114 | + scope={{ kind: 'workspace', workspaceId }} |
| 115 | + target={{ kind: 'integration', id: integration.type }} |
| 116 | + label={integration.name} |
| 117 | + variant='primary' |
| 118 | + /> |
| 119 | + ) : undefined |
| 120 | + } |
| 121 | + /> |
| 122 | + ) |
| 123 | +} |
| 124 | + |
| 125 | +interface IntegrationAccessNoticeProps { |
| 126 | + workspaceId: string |
| 127 | + title: string |
| 128 | + description: string |
| 129 | + action?: ReactNode |
| 130 | +} |
| 131 | + |
| 132 | +function IntegrationAccessNotice({ |
| 133 | + workspaceId, |
| 134 | + title, |
| 135 | + description, |
| 136 | + action, |
| 137 | +}: IntegrationAccessNoticeProps) { |
83 | 138 | return ( |
84 | 139 | <div className='flex h-full flex-col bg-[var(--bg)]'> |
85 | 140 | <div className={PAGE_HEADER_BAR}> |
86 | 141 | <ChipLink href={`/workspace/${workspaceId}/integrations`} leftIcon={ArrowLeft}> |
87 | 142 | Integrations |
88 | 143 | </ChipLink> |
89 | 144 | </div> |
90 | | - <EmptyState |
91 | | - title='Access required' |
92 | | - description={ |
93 | | - requestable |
94 | | - ? 'Ask an administrator for access.' |
95 | | - : 'Your organization restricts this integration.' |
96 | | - } |
97 | | - action={ |
98 | | - requestable ? ( |
99 | | - <RequestAccessAction |
100 | | - scope={{ kind: 'workspace', workspaceId }} |
101 | | - target={{ kind: 'integration', id: integration.type }} |
102 | | - label={integration.name} |
103 | | - variant='primary' |
104 | | - /> |
105 | | - ) : undefined |
106 | | - } |
107 | | - /> |
| 145 | + <EmptyState title={title} description={description} action={action} /> |
108 | 146 | </div> |
109 | 147 | ) |
110 | 148 | } |
|
0 commit comments