forked from baron/baron-sso
devfront RP 설정 표 공통화 및 레이아웃 정리
This commit is contained in:
@@ -29,6 +29,23 @@ function mergeTomlObjects(base: TomlObject, override: TomlObject): TomlObject {
|
||||
return result;
|
||||
}
|
||||
|
||||
function setTomlValue(
|
||||
target: TomlObject,
|
||||
path: string[],
|
||||
value: TomlValue,
|
||||
): void {
|
||||
let cursor: TomlObject = target;
|
||||
for (let index = 0; index < path.length - 1; index += 1) {
|
||||
const key = path[index];
|
||||
const current = cursor[key];
|
||||
if (!current || typeof current === "string") {
|
||||
cursor[key] = {};
|
||||
}
|
||||
cursor = cursor[key] as TomlObject;
|
||||
}
|
||||
cursor[path[path.length - 1]] = value;
|
||||
}
|
||||
|
||||
function isSupportedLocale(value: string): value is Locale {
|
||||
return (SUPPORTED_LOCALES as readonly string[]).includes(value);
|
||||
}
|
||||
@@ -82,7 +99,7 @@ function parseToml(raw: string): TomlObject {
|
||||
cursor = cursor[section] as TomlObject;
|
||||
}
|
||||
|
||||
cursor[key] = value;
|
||||
setTomlValue(cursor, key.split("."), value);
|
||||
}
|
||||
|
||||
return root;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
130
devfront/src/features/clients/components/SettingsTable.tsx
Normal file
130
devfront/src/features/clients/components/SettingsTable.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import type * as React from "react";
|
||||
import { cn } from "../../../lib/utils";
|
||||
|
||||
interface SettingsTableShellProps {
|
||||
className?: string;
|
||||
bodyClassName?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function SettingsTableShell({
|
||||
className,
|
||||
bodyClassName,
|
||||
children,
|
||||
}: SettingsTableShellProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"overflow-hidden rounded-md border border-border bg-background",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className={cn("overflow-auto", bodyClassName)}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsTable({
|
||||
className,
|
||||
...props
|
||||
}: React.TableHTMLAttributes<HTMLTableElement>) {
|
||||
return <table className={cn("w-full text-sm", className)} {...props} />;
|
||||
}
|
||||
|
||||
function SettingsTableHeader({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLTableSectionElement>) {
|
||||
return (
|
||||
<thead
|
||||
className={cn(
|
||||
"bg-muted/50 border-b border-border text-xs uppercase tracking-wider text-muted-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsTableBody({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLTableSectionElement>) {
|
||||
return (
|
||||
<tbody className={cn("divide-y divide-border", className)} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsTableRow({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLTableRowElement>) {
|
||||
return (
|
||||
<tr
|
||||
className={cn(
|
||||
"border-b transition-colors hover:bg-muted/20 data-[state=selected]:bg-muted",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsTableHead({
|
||||
className,
|
||||
...props
|
||||
}: React.ThHTMLAttributes<HTMLTableCellElement>) {
|
||||
return (
|
||||
<th
|
||||
className={cn(
|
||||
"h-12 px-4 text-left text-xs font-bold uppercase tracking-wider text-black align-middle dark:text-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsTableCell({
|
||||
className,
|
||||
...props
|
||||
}: React.TdHTMLAttributes<HTMLTableCellElement>) {
|
||||
return <td className={cn("px-4 py-3 align-top", className)} {...props} />;
|
||||
}
|
||||
|
||||
interface SettingsTableEmptyStateProps {
|
||||
colSpan: number;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function SettingsTableEmptyState({
|
||||
colSpan,
|
||||
children,
|
||||
className,
|
||||
}: SettingsTableEmptyStateProps) {
|
||||
return (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={colSpan}
|
||||
className={cn(
|
||||
"px-4 py-8 text-center text-sm text-muted-foreground",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
SettingsTable,
|
||||
SettingsTableBody,
|
||||
SettingsTableCell,
|
||||
SettingsTableEmptyState,
|
||||
SettingsTableHead,
|
||||
SettingsTableHeader,
|
||||
SettingsTableRow,
|
||||
SettingsTableShell,
|
||||
};
|
||||
@@ -6,6 +6,32 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe("i18n", () => {
|
||||
it("returns Korean copy for dotted developer claim headers", () => {
|
||||
window.localStorage.setItem("locale", "ko");
|
||||
|
||||
expect(
|
||||
t("ui.dev.clients.general.id_token_claims.table.key", "Claim Key"),
|
||||
).toBe("클레임 키");
|
||||
expect(
|
||||
t(
|
||||
"ui.dev.clients.general.id_token_claims.table.value_type",
|
||||
"Value Type",
|
||||
),
|
||||
).toBe("값 유형");
|
||||
expect(
|
||||
t(
|
||||
"msg.dev.clients.general.id_token_claims.hint",
|
||||
"RP 전용 확장 claim을 구분해서 관리합니다. 사용자별 claim 값은 동의 및 Claims 탭에서 수정합니다.",
|
||||
),
|
||||
).toBe("사용자별 claim 값은 동의 및 Claims 탭에서 수정합니다.");
|
||||
expect(
|
||||
t(
|
||||
"msg.dev.clients.general.id_token_claims.preview_hint",
|
||||
"저장될 metadata.id_token_claims 구조를 미리 확인할 수 있습니다.",
|
||||
),
|
||||
).toBe("설정 저장 시 반영될 claim 구성을 미리 볼 수 있습니다.");
|
||||
});
|
||||
|
||||
it("returns English copy for the developer request and grants screens", () => {
|
||||
window.localStorage.setItem("locale", "en");
|
||||
|
||||
@@ -32,5 +58,27 @@ describe("i18n", () => {
|
||||
"현재 부여된 개발자 권한 목록입니다.",
|
||||
),
|
||||
).toBe("Current developer access grants.");
|
||||
expect(
|
||||
t(
|
||||
"msg.dev.clients.general.id_token_claims.subtitle",
|
||||
"RP 전용 확장 claim을 구분해서 관리합니다.",
|
||||
),
|
||||
).toBe(
|
||||
"User-specific claim values are edited in the Consent and Claims tabs.",
|
||||
);
|
||||
expect(
|
||||
t(
|
||||
"msg.dev.clients.general.id_token_claims.hint",
|
||||
"사용자별 claim 값은 동의 및 Claims 탭에서 수정합니다.",
|
||||
),
|
||||
).toBe(
|
||||
"User-specific claim values are edited in the Consent and Claims tabs.",
|
||||
);
|
||||
expect(
|
||||
t(
|
||||
"msg.dev.clients.general.id_token_claims.preview_hint",
|
||||
"설정 저장 시 반영될 claim 구성을 미리 볼 수 있습니다.",
|
||||
),
|
||||
).toBe("Preview the claim set that will be saved with these settings.");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1674,6 +1674,11 @@ value_type_object = "Object"
|
||||
key_placeholder = "e.g. locale"
|
||||
value_placeholder = "Enter the default value"
|
||||
|
||||
[msg.dev.clients.general.id_token_claims]
|
||||
subtitle = "User-specific claim values are edited in the Consent and Claims tabs."
|
||||
hint = "User-specific claim values are edited in the Consent and Claims tabs."
|
||||
preview_hint = "Preview the claim set that will be saved with these settings."
|
||||
|
||||
[ui.dev.clients.general.security]
|
||||
private = "Server Side App"
|
||||
pkce = "PKCE"
|
||||
|
||||
@@ -463,8 +463,8 @@ offline_access_condition_grant_type = "client grant_types에 refresh_token 포
|
||||
[msg.dev.clients.general.id_token_claims]
|
||||
subtitle = "RP 전용 확장 claim을 구분해서 관리합니다."
|
||||
empty = "아직 추가된 ID Token claim이 없습니다."
|
||||
hint = "RP 전용 확장 claim을 구분해서 관리합니다. 사용자별 claim 값은 동의 및 Claims 탭에서 수정합니다."
|
||||
preview_hint = "저장될 metadata.id_token_claims 구조를 미리 확인할 수 있습니다."
|
||||
hint = "사용자별 claim 값은 동의 및 Claims 탭에서 수정합니다."
|
||||
preview_hint = "설정 저장 시 반영될 claim 구성을 미리 볼 수 있습니다."
|
||||
key_required = "Claim key를 입력해야 합니다."
|
||||
reserved_key = "`rp_claims`는 예약된 namespace 키입니다."
|
||||
duplicate_key = "중복된 claim key가 있습니다: {{namespace}}.{{key}}"
|
||||
@@ -1655,10 +1655,10 @@ namespace_rp_claims = "rp_claims"
|
||||
nullable_label = "Nullable"
|
||||
read_user_allowed_label = "사용자 읽기 허용"
|
||||
write_user_allowed_label = "사용자 쓰기 허용"
|
||||
table.key = "Claim Key"
|
||||
table.namespace = "Namespace"
|
||||
table.value_type = "Value Type"
|
||||
table.nullable = "Nullable"
|
||||
table.key = "클레임 키"
|
||||
table.namespace = "네임스페이스"
|
||||
table.value_type = "값 유형"
|
||||
table.nullable = "Null 허용"
|
||||
table.read_user_allowed = "사용자 읽기"
|
||||
table.write_user_allowed = "사용자 쓰기"
|
||||
table.default_value = "기본값"
|
||||
|
||||
@@ -1723,6 +1723,11 @@ value_type_object = ""
|
||||
key_placeholder = ""
|
||||
value_placeholder = ""
|
||||
|
||||
[msg.dev.clients.general.id_token_claims]
|
||||
subtitle = ""
|
||||
hint = ""
|
||||
preview_hint = ""
|
||||
|
||||
[ui.dev.clients.general.security]
|
||||
private = ""
|
||||
pkce = ""
|
||||
|
||||
Reference in New Issue
Block a user