feat(dashboard): Members tab on the app detail page
A new "Members" tab is rendered between Domains and Settings for callers whose `my_role` on the app is `app_admin` (owners always; explicit member-app_admins; admins do not see it — they're only implicit editors and can't manage memberships). The tab lets the caller: - See every explicit member of the app with username, email, instance- role chip, app-role chip, and joined date. Inactive users render greyed-out so admins know the row exists. - Pick a `member`-instance user from a dropdown and grant viewer / editor / app_admin access. The dropdown is populated from `/admin/admins` filtered to active members not already on the app. - Promote / demote / remove existing members via the shared `ActionMenu` kebab. Removal goes through `ConfirmModal`. Member-with-app_admin callers see a disabled add form with an explanatory message — they have authority to manage memberships but can't browse the user directory (gated on `InstanceManageUsers`), which is a known phase-3.5 caveat to revisit in a follow-up. Also extends `RoleChip` with an `appRole` prop and palette for app roles, and adds an `appMembers` namespace to api.ts mirroring the `domains` shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,24 @@
|
||||
<script lang="ts">
|
||||
import type { InstanceRole } from '$lib/auth';
|
||||
import type { AppRole } from '$lib/api';
|
||||
|
||||
interface Props {
|
||||
role: InstanceRole;
|
||||
role?: InstanceRole;
|
||||
appRole?: AppRole;
|
||||
size?: 'sm' | 'md';
|
||||
}
|
||||
|
||||
let { role, size = 'md' }: Props = $props();
|
||||
let { role, appRole, size = 'md' }: Props = $props();
|
||||
|
||||
// Display label: app roles read better with a space ("app admin")
|
||||
// than their wire form ("app_admin").
|
||||
const label = $derived(
|
||||
appRole ? appRole.replace('_', ' ') : (role ?? '')
|
||||
);
|
||||
const cls = $derived(appRole ? `chip-${appRole}` : `chip-${role}`);
|
||||
</script>
|
||||
|
||||
<span class="chip chip-{role}" class:sm={size === 'sm'}>{role}</span>
|
||||
<span class="chip {cls}" class:sm={size === 'sm'}>{label}</span>
|
||||
|
||||
<style>
|
||||
.chip {
|
||||
@@ -42,4 +51,19 @@
|
||||
color: #cbd5e1;
|
||||
border-color: #334155;
|
||||
}
|
||||
.chip-app_admin {
|
||||
background: #4c1d95;
|
||||
color: #c4b5fd;
|
||||
border-color: #6d28d9;
|
||||
}
|
||||
.chip-editor {
|
||||
background: #1e3a8a;
|
||||
color: #93c5fd;
|
||||
border-color: #1d4ed8;
|
||||
}
|
||||
.chip-viewer {
|
||||
background: #1f2937;
|
||||
color: #9ca3af;
|
||||
border-color: #374151;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user