1
0
forked from baron/baron-sso

관계 조회 권한 사용자 검색 안내 강화

This commit is contained in:
2026-04-20 14:54:53 +09:00
parent 7e0680a71c
commit ea387ff6f2
2 changed files with 59 additions and 12 deletions

View File

@@ -32,6 +32,8 @@ import {
removeClientRelation,
} from "../../lib/devApi";
import { t } from "../../lib/i18n";
import { resolveProfileRole } from "../../lib/role";
import { useAuth } from "react-oidc-context";
import { ClientDetailTabs } from "./ClientDetailTabs";
const relationOptions = [
@@ -68,6 +70,7 @@ function formatUserLabel(user: DevAssignableUser) {
function ClientRelationsPage() {
const params = useParams();
const auth = useAuth();
const queryClient = useQueryClient();
const clientId = params.id ?? "";
const [selectedRelations, setSelectedRelations] = useState<RelationOption[]>(
@@ -79,6 +82,11 @@ function ClientRelationsPage() {
null,
);
const [isSearchOpen, setIsSearchOpen] = useState(false);
const systemRole = resolveProfileRole(
auth.user?.profile as Record<string, unknown> | undefined,
);
const { data: clientData } = useQuery({
queryKey: ["client", clientId],
queryFn: () => fetchClient(clientId),
@@ -95,6 +103,19 @@ function ClientRelationsPage() {
enabled: clientId.length > 0,
});
// Calculate permissions for UI hints and button states
const isSuperAdmin = systemRole === "super_admin";
const myUserId = auth.user?.profile.sub;
const isRpAdmin = useMemo(() => {
if (isSuperAdmin) return true;
if (!relationData?.items || !myUserId) return false;
return relationData.items.some(
(item) => item.subject === `User:${myUserId}` && item.relation === "admins"
);
}, [relationData?.items, myUserId, isSuperAdmin]);
const canManageRelations = isRpAdmin || isSuperAdmin;
const isRelationshipViewForbidden =
(error as AxiosError | null)?.response?.status === 403;
const relationshipViewForbiddenMessage = t(
@@ -221,6 +242,16 @@ function ClientRelationsPage() {
});
const handleAdd = () => {
if (!canManageRelations) {
toast(
t(
"msg.dev.clients.relationships.add_forbidden_viewer",
"'관계 조회' 권한만으로는 새로운 관계를 추가하거나 사용자를 검색할 수 없습니다. 'RP 관리자' 권한이 필요합니다.",
),
"error",
);
return;
}
if (!selectedUser) {
toast(
t(
@@ -398,18 +429,26 @@ function ClientRelationsPage() {
)}
</div>
) : isUserSearchForbidden ? (
<div className="px-3 py-2 text-sm text-destructive font-medium">
{t(
"msg.dev.clients.relationships.search_forbidden_user",
"일반 사용자는 관계 추가를 위한 사용자 검색을 사용할 수 없습니다.",
)}
<div className="px-4 py-8 text-center text-sm text-destructive font-medium border-b border-border/40 bg-destructive/5 flex flex-col gap-2">
<p>
{t(
"msg.dev.clients.relationships.search_forbidden_user",
"일반 사용자는 관계 추가를 위한 사용자 검색을 사용할 수 없습니다.",
)}
</p>
<p className="text-xs text-muted-foreground/80 font-normal">
{t(
"msg.dev.clients.relationships.search_forbidden_user_hint",
"'관계 조회' 권한만으로는 사용자 검색이 제한됩니다. 'RP 관리자' 관계가 필요합니다.",
)}
</p>
</div>
) : (userSearchData?.items ?? []).length > 0 ? (
(userSearchData?.items ?? []).map((user) => (
<button
key={user.id}
type="button"
className="flex w-full flex-col gap-1 px-3 py-2 text-left hover:bg-muted/40"
className="flex w-full flex-col gap-1 px-3 py-2 text-left hover:bg-muted/40 border-b border-border/40 last:border-b-0"
onMouseDown={(event) => {
event.preventDefault();
handleSelectUser(user);
@@ -425,7 +464,7 @@ function ClientRelationsPage() {
</button>
))
) : (
<div className="px-3 py-2 text-sm text-muted-foreground">
<div className="px-3 py-4 text-center text-sm text-muted-foreground">
{t(
"msg.dev.clients.relationships.search_empty",
"검색 결과가 없습니다.",
@@ -503,7 +542,7 @@ function ClientRelationsPage() {
<div className="flex justify-end">
<Button
onClick={handleAdd}
disabled={addMutation.isPending}
disabled={addMutation.isPending || !canManageRelations}
className="gap-2"
>
<Plus className="h-4 w-4" />
@@ -625,7 +664,7 @@ function ClientRelationsPage() {
variant="ghost"
size="sm"
className="gap-2 text-destructive hover:text-destructive"
disabled={removeMutation.isPending}
disabled={removeMutation.isPending || !canManageRelations}
onClick={() =>
handleRemove(item.relation, item.subject)
}