mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 02:15:05 +03:00
feat: display components for interstitial
This commit is contained in:
1 parent
2b30614b4d
commit
f63b9c01a4
7 files changed
+185
No files matched your search
+22
@@ -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()
|
||||
})
|
||||
})
|
||||
+13
@@ -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>
|
||||
}
|
||||
+20
@@ -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)
|
||||
})
|
||||
})
|
||||
+10
@@ -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'
|
||||
}
|
||||
+58
@@ -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'
|
||||
Reference in new issue
Block a user