e6294ce79c
Browser QA на staging показал что UUID actor'а render'ится raw во многих местах кроме drawer (MR !158 — там был inline fix): - /reviews Records tab — column AUTHOR - /reviews Schemas tab — column AUTHOR - /audit log — column USER 77ec2cc8-98e3-4d70-8037-94038fcbde67 — нечитаемо для оператора. Тот же паттерн что я делал в MR !158, теперь shared helper: src/lib/useUserDisplay.tsx: - useUserDisplay(uuid) hook — returns { display, isMe, fullId } - UserCell component — readonly cell с title=fullId tooltip - Match current user (JWT sub claim) → 'Я • <preferred_username>' - Other user → short UUID (8 chars) + full в title - null/undefined → 'anonymous' Применено: - reviews.tsx:334,625 — records + schemas table AUTHOR cells - audit.tsx:476 — USER cell - SchemaDraftDrawer.tsx — refactor (был inline в MR !158 → теперь shared) TODO Phase 1d: backend endpoint /admin/users/{sub}/display чтобы и для не-current-user отображать username.
63 lines
2.2 KiB
TypeScript
63 lines
2.2 KiB
TypeScript
import { useAuth } from 'react-oidc-context'
|
||
|
||
/**
|
||
* Resolve UUID sub claim (Keycloak {@code sub} → makerId / userId) к
|
||
* human-friendly display string.
|
||
*
|
||
* <p>Cases:
|
||
* <ul>
|
||
* <li><b>uuid matches current user</b> → {@code "Я • <preferred_username>"}
|
||
* (или just {@code "Я"} если profile неполный).</li>
|
||
* <li><b>different user</b> → first 8 chars of UUID. Full ID в {@code fullId}
|
||
* чтобы consumer мог положить в {@code title=...} для tooltip.</li>
|
||
* <li><b>null/undefined uuid</b> → {@code "anonymous"}.</li>
|
||
* </ul>
|
||
*
|
||
* <p>TODO Phase 1d: для не-current-user backend endpoint
|
||
* {@code /admin/users/{sub}/display} → resolve в username. Пока best-effort:
|
||
* current user resolved через JWT profile claim, остальные — UUID prefix
|
||
* (achievable via JWT alone).
|
||
*
|
||
* <p>Используется везде где UI показывает actor: AUTHOR в таблицах
|
||
* /reviews (record + schema), USER в /audit log, AUTHOR в schema draft
|
||
* drawer.
|
||
*/
|
||
export function useUserDisplay(uuid: string | null | undefined): {
|
||
display: string
|
||
isMe: boolean
|
||
fullId: string
|
||
} {
|
||
const auth = useAuth()
|
||
const fullId = uuid ?? ''
|
||
if (!fullId) {
|
||
return { display: 'anonymous', isMe: false, fullId: '' }
|
||
}
|
||
const currentSub = auth.user?.profile?.sub
|
||
const isMe = Boolean(currentSub) && currentSub === fullId
|
||
if (isMe) {
|
||
const me =
|
||
auth.user?.profile?.preferred_username ||
|
||
auth.user?.profile?.name ||
|
||
auth.user?.profile?.email
|
||
return { display: me ? `Я • ${me}` : 'Я', isMe, fullId }
|
||
}
|
||
return { display: fullId.substring(0, 8), isMe, fullId }
|
||
}
|
||
|
||
/**
|
||
* UserCell — convenience component для тех мест где нужен readonly displaу
|
||
* с tooltip. Используется в таблицах.
|
||
*/
|
||
export function UserCell({ uuid }: { uuid: string | null | undefined }) {
|
||
const { display, isMe, fullId } = useUserDisplay(uuid)
|
||
if (!fullId) return <span className="text-mute italic">anonymous</span>
|
||
return (
|
||
<span
|
||
className={isMe ? 'text-ink' : 'font-mono text-ink-2'}
|
||
title={fullId}
|
||
>
|
||
{display}
|
||
</span>
|
||
)
|
||
}
|