feat: add About Me dialog showing user info, roles, and groups
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m48s
CI / docker (push) Successful in 1m45s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s

- Add GET /api/v1/auth/me endpoint returning current user's UserDetail
- Add AboutMeDialog component with role badges and group memberships
- Add userMenuItems prop to TopBar via design-system update
- Wire "About Me" menu item into user dropdown above Logout

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-08 12:12:29 +02:00
parent a8b977a2db
commit 448a63adc9
7 changed files with 213 additions and 7 deletions

View File

@@ -0,0 +1,74 @@
import { Modal, Badge, MonoText, Spinner, SectionHeader } from '@cameleer/design-system';
import { useMe } from '../api/queries/auth';
import styles from './AboutMeDialog.module.css';
interface AboutMeDialogProps {
open: boolean;
onClose: () => void;
}
export function AboutMeDialog({ open, onClose }: AboutMeDialogProps) {
const { data: user, isLoading } = useMe(open);
return (
<Modal open={open} onClose={onClose} title="About Me" size="sm">
{isLoading || !user ? (
<div className={styles.loading}><Spinner size="md" /></div>
) : (
<div className={styles.content}>
<div className={styles.metaGrid}>
<span className={styles.metaLabel}>Name</span>
<span className={styles.metaValue}>{user.displayName}</span>
<span className={styles.metaLabel}>User ID</span>
<MonoText size="xs">{user.userId}</MonoText>
<span className={styles.metaLabel}>Provider</span>
<span className={styles.metaValue}>{user.provider}</span>
{user.email && (
<>
<span className={styles.metaLabel}>Email</span>
<span className={styles.metaValue}>{user.email}</span>
</>
)}
<span className={styles.metaLabel}>Created</span>
<span className={styles.metaValue}>
{new Date(user.createdAt).toLocaleDateString()}
</span>
</div>
<SectionHeader>Roles</SectionHeader>
<div className={styles.tagList}>
{user.effectiveRoles.length === 0 && (
<span className={styles.empty}>No roles assigned</span>
)}
{user.effectiveRoles.map((role) => (
<Badge
key={role.id}
label={role.name}
color={role.name === 'ADMIN' ? 'error' : role.name === 'OPERATOR' ? 'warning' : 'auto'}
/>
))}
</div>
{user.effectiveRoles.some((r) => r.source !== 'direct') && (
<div className={styles.sourceList}>
{user.effectiveRoles.map((role) => (
<span key={role.id} className={styles.sourceNote}>
{role.name}: {role.source}
</span>
))}
</div>
)}
<SectionHeader>Groups</SectionHeader>
<div className={styles.tagList}>
{user.effectiveGroups.length === 0 && (
<span className={styles.empty}>No group memberships</span>
)}
{user.effectiveGroups.map((group) => (
<Badge key={group.id} label={group.name} color="auto" />
))}
</div>
</div>
)}
</Modal>
);
}