feat: display components for interstitial

This commit is contained in:
kemal committed 2026-09-07 14:19:00 +01:00
1 parent 2b30614b4d
commit f63b9c01a4
7 files changed
+185

No files matched your search

@@ -0,0 +1,22 @@
import { screen } from '@testing-library/react'
import { describe, expect, test } from 'vitest'
import { OverRoleAnnotation } from './OverRoleAnnotation'
import { customRender } from '@/tests/lib/custom-render'
describe('OverRoleAnnotation', () => {
test('renders when the requested level exceeds the role', () => {
customRender(<OverRoleAnnotation level="write" memberRole="Read-only" />)
expect(screen.getByText('Read-only for your role')).toBeInTheDocument()
})
test('renders nothing when the role covers the requested level', () => {
customRender(<OverRoleAnnotation level="read" memberRole="Read-only" />)
expect(screen.queryByText('Read-only for your role')).not.toBeInTheDocument()
})
test('renders nothing for a role that can write', () => {
customRender(<OverRoleAnnotation level="read_write" memberRole="Developer" />)
expect(screen.queryByText('Read-only for your role')).not.toBeInTheDocument()
})
})
@@ -0,0 +1,13 @@
import { isScopeGroupOverRole } from './OverRoleAnnotation.utils'
import type { OAuthOrganizationRole, OAuthScopeLevel } from '@/data/oauth-apps/types'
export interface OverRoleAnnotationProps {
level: OAuthScopeLevel
memberRole: OAuthOrganizationRole['role']
}
export const OverRoleAnnotation = ({ level, memberRole }: OverRoleAnnotationProps) => {
if (!isScopeGroupOverRole(level, memberRole)) return null
return <span className="text-xs text-foreground-lighter shrink-0">Read-only for your role</span>
}
@@ -0,0 +1,20 @@
import { describe, expect, test } from 'vitest'
import { isScopeGroupOverRole } from './OverRoleAnnotation.utils'
describe('isScopeGroupOverRole', () => {
test('flags write and read_write levels for a Read-only role', () => {
expect(isScopeGroupOverRole('write', 'Read-only')).toBe(true)
expect(isScopeGroupOverRole('read_write', 'Read-only')).toBe(true)
})
test('does not flag a read level for a Read-only role', () => {
expect(isScopeGroupOverRole('read', 'Read-only')).toBe(false)
})
test('does not flag any level for roles that can write', () => {
expect(isScopeGroupOverRole('read_write', 'Developer')).toBe(false)
expect(isScopeGroupOverRole('write', 'Owner')).toBe(false)
expect(isScopeGroupOverRole('read', 'Developer')).toBe(false)
})
})
@@ -0,0 +1,10 @@
import type { OAuthOrganizationRole, OAuthScopeLevel } from '@/data/oauth-apps/types'
// Role capability is binary for now: a "Read-only" role can't satisfy write scopes, and every
// other role (Developer, Owner, ...) is assumed to satisfy whatever the app requests.
export function isScopeGroupOverRole(
level: OAuthScopeLevel,
role: OAuthOrganizationRole['role']
): boolean {
return role === 'Read-only' && level !== 'read'
}
@@ -0,0 +1,58 @@
import { screen } from '@testing-library/react'
import { describe, expect, test } from 'vitest'
import { ScopeGroupCard } from './ScopeGroupCard'
import type { OAuthScopeGroup } from '@/data/oauth-apps/types'
import { customRender } from '@/tests/lib/custom-render'
const SCOPE_GROUPS: OAuthScopeGroup[] = [
{
name: 'Project Settings, Action Runs, Logs, SQL Snippets',
level: 'read_write',
scopes: ['project_settings', 'action_runs', 'logs', 'sql_snippets'],
},
{
name: 'Database Webhooks, Development Branches, Production Branches',
level: 'read',
scopes: ['database_webhooks', 'development_branches', 'production_branches'],
},
]
describe('ScopeGroupCard', () => {
test('interpolates the app name into the intro line', () => {
customRender(
<ScopeGroupCard appName="Vercel" scopeGroups={SCOPE_GROUPS} memberRole="Developer" />
)
expect(
screen.getByText(
'Authorizing Vercel grants it the following access permissions to the selected projects.'
)
).toBeInTheDocument()
})
test('renders READ and READ + WRITE badge labels', () => {
customRender(
<ScopeGroupCard appName="Vercel" scopeGroups={SCOPE_GROUPS} memberRole="Developer" />
)
expect(screen.getByText('READ + WRITE')).toBeInTheDocument()
expect(screen.getByText('READ')).toBeInTheDocument()
})
test('shows the over-role annotation only for groups that exceed a Read-only role', () => {
customRender(
<ScopeGroupCard appName="Vercel" scopeGroups={SCOPE_GROUPS} memberRole="Read-only" />
)
expect(screen.getAllByText('Read-only for your role')).toHaveLength(1)
})
test('shows no over-role annotation for a role that can write', () => {
customRender(
<ScopeGroupCard appName="Vercel" scopeGroups={SCOPE_GROUPS} memberRole="Developer" />
)
expect(screen.queryByText('Read-only for your role')).not.toBeInTheDocument()
})
})
@@ -0,0 +1,57 @@
import { Badge, Card, CardContent } from 'ui'
import { OverRoleAnnotation } from './OverRoleAnnotation'
import type {
OAuthOrganizationRole,
OAuthScopeGroup,
OAuthScopeLevel,
} from '@/data/oauth-apps/types'
export interface ScopeGroupCardProps {
appName: string
scopeGroups: OAuthScopeGroup[]
memberRole: OAuthOrganizationRole['role']
}
export const ScopeGroupCard = ({ appName, scopeGroups, memberRole }: ScopeGroupCardProps) => {
return (
<section className="flex flex-col">
<div>
<p className="text-xs font-medium uppercase tracking-wider text-foreground-light">
Permissions requested
</p>
<p className="mt-1 text-xs text-foreground-lighter">
Authorizing {appName} grants it the following access permissions to the selected projects.
</p>
</div>
<Card className="overflow-hidden shadow-none bg-surface-200/60 border-muted mt-3">
<CardContent className="border-none p-0">
<div className="divide-y divide-muted px-4">
{scopeGroups.map((scopeGroup) => (
<div key={scopeGroup.name} className="py-3">
<div className="flex items-center justify-between gap-3">
<Badge variant={getScopeLevelBadgeVariant(scopeGroup.level)}>
{getScopeLevelLabel(scopeGroup.level)}
</Badge>
<OverRoleAnnotation level={scopeGroup.level} memberRole={memberRole} />
</div>
<p className="mt-1 text-sm text-foreground-light">{scopeGroup.scopes.join(', ')}</p>
</div>
))}
</div>
</CardContent>
</Card>
</section>
)
}
function getScopeLevelLabel(level: OAuthScopeLevel) {
if (level === 'read') return 'READ'
if (level === 'write') return 'WRITE'
return 'READ + WRITE'
}
function getScopeLevelBadgeVariant(level: OAuthScopeLevel) {
return level === 'read' ? 'default' : 'warning'
}
@@ -0,0 +1,5 @@
export { OverRoleAnnotation } from './OverRoleAnnotation'
export type { OverRoleAnnotationProps } from './OverRoleAnnotation'
export { isScopeGroupOverRole } from './OverRoleAnnotation.utils'
export { ScopeGroupCard } from './ScopeGroupCard'
export type { ScopeGroupCardProps } from './ScopeGroupCard'