1
0
forked from baron/baron-sso

i18n refresh and frontend fixes

This commit is contained in:
Lectom C Han
2026-02-10 19:15:51 +09:00
parent 2441c64598
commit b6d3b69cda
44 changed files with 8603 additions and 1760 deletions

View File

@@ -13,7 +13,6 @@ import { Button } from "../../components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "../../components/ui/card";
@@ -27,6 +26,7 @@ import {
TableRow,
} from "../../components/ui/table";
import { fetchClient, fetchConsents, revokeConsent } from "../../lib/devApi";
import { t } from "../../lib/i18n";
function ClientConsentsPage() {
const params = useParams();
@@ -65,17 +65,20 @@ function ClientConsentsPage() {
<div className="space-y-2">
<nav className="flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
<Link to="/" className="hover:text-primary">
Home
{t("ui.dev.clients.consents.breadcrumb.home", "Home")}
</Link>
<span>/</span>
<Link to="/clients" className="hover:text-primary">
Clients
{t("ui.dev.clients.consents.breadcrumb.clients", "Clients")}
</Link>
<span>/</span>
<span>{clientData?.client?.name || clientId}</span>
<span>/</span>
<span className="text-foreground font-semibold">
User Consent Grants
{t(
"ui.dev.clients.consents.breadcrumb.current",
"User Consent Grants",
)}
</span>
</nav>
<div className="flex items-center gap-2">
@@ -86,10 +89,13 @@ function ClientConsentsPage() {
</Button>
<div>
<p className="text-3xl font-black leading-tight">
User Consent Grants
{t("ui.dev.clients.consents.title", "User Consent Grants")}
</p>
<p className="text-muted-foreground">
OIDC Relying Party ·.
{t(
"msg.dev.clients.consents.subtitle",
"OIDC Relying Party 사용자 권한을 검토·관리합니다.",
)}
</p>
</div>
</div>
@@ -100,7 +106,9 @@ function ClientConsentsPage() {
clientData?.client?.status === "active" ? "success" : "muted"
}
>
{clientData?.client?.status === "active" ? "Active" : "Inactive"}
{clientData?.client?.status === "active"
? t("ui.common.status.active", "Active")
: t("ui.common.status.inactive", "Inactive")}
</Badge>
</div>
</div>
@@ -109,16 +117,16 @@ function ClientConsentsPage() {
to={`/clients/${clientId}`}
className="whitespace-nowrap border-b-2 border-transparent text-muted-foreground hover:text-foreground"
>
Connection
{t("ui.dev.clients.details.tab.connection", "Connection")}
</Link>
<span className="whitespace-nowrap border-b-2 border-primary pb-1 text-primary">
Consent &amp; Users
{t("ui.dev.clients.details.tab.consents", "Consent & Users")}
</span>
<Link
to={`/clients/${clientId}/settings`}
className="whitespace-nowrap border-b-2 border-transparent text-muted-foreground hover:text-foreground"
>
Settings
{t("ui.dev.clients.details.tab.settings", "Settings")}
</Link>
</div>
</header>
@@ -130,34 +138,48 @@ function ClientConsentsPage() {
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input
className="pl-10"
placeholder="사용자 ID, 이름, 이메일로 검색"
placeholder={t(
"ui.dev.clients.consents.search_placeholder",
"사용자 ID, 이름, 이메일로 검색",
)}
value={subjectInput}
onChange={(e) => setSubjectInput(e.target.value)}
/>
</div>
<div className="flex items-center gap-2">
<span className="text-xs font-bold uppercase tracking-wider text-muted-foreground">
Status:
{t("ui.dev.clients.consents.status_label", "Status:")}
</span>
<select className="h-10 rounded-lg border border-input bg-background px-3 text-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/30">
<option>All Statuses</option>
<option selected>Active</option>
<option>Revoked</option>
<option>
{t("ui.dev.clients.consents.status_all", "All Statuses")}
</option>
<option selected>
{t("ui.common.status.active", "Active")}
</option>
<option>
{t("ui.dev.clients.consents.status_revoked", "Revoked")}
</option>
</select>
</div>
</div>
<div className="flex items-center gap-3">
<Button variant="ghost" className="gap-1 text-muted-foreground">
<Filter className="h-4 w-4" />
Advanced Filters
{t(
"ui.dev.clients.consents.filters.advanced",
"Advanced Filters",
)}
</Button>
<Button
className="shadow-sm shadow-primary/30"
onClick={() => setSubject(subjectInput.trim())}
>
{t("ui.common.search", "검색")}
</Button>
<Button className="shadow-sm shadow-primary/30">
{t("ui.dev.clients.consents.export_csv", "Export CSV")}
</Button>
<Button className="shadow-sm shadow-primary/30">Export CSV</Button>
</div>
</CardContent>
</Card>
@@ -165,32 +187,58 @@ function ClientConsentsPage() {
<Card className="glass-panel">
{error && (
<CardContent className="text-sm text-red-500">
Error loading consents: {(error as Error).message}
{t(
"msg.dev.clients.consents.load_error",
"Error loading consents: {{error}}",
{
error: (error as Error).message,
},
)}
</CardContent>
)}
{isLoading && (
<CardContent className="text-sm text-muted-foreground">
Loading consents...
{t("msg.dev.clients.consents.loading", "Loading consents...")}
</CardContent>
)}
<Table>
<TableHeader>
<TableRow>
<TableHead>User</TableHead>
<TableHead>Tenant</TableHead>
<TableHead>Status</TableHead>
<TableHead>Granted Scopes</TableHead>
<TableHead>First Granted</TableHead>
<TableHead>Last Authenticated</TableHead>
<TableHead className="text-right">Action</TableHead>
<TableHead>
{t("ui.dev.clients.consents.table.user", "User")}
</TableHead>
<TableHead>
{t("ui.dev.clients.consents.table.tenant", "Tenant")}
</TableHead>
<TableHead>
{t("ui.dev.clients.consents.table.status", "Status")}
</TableHead>
<TableHead>
{t("ui.dev.clients.consents.table.scopes", "Granted Scopes")}
</TableHead>
<TableHead>
{t(
"ui.dev.clients.consents.table.first_granted",
"First Granted",
)}
</TableHead>
<TableHead>
{t(
"ui.dev.clients.consents.table.last_auth",
"Last Authenticated",
)}
</TableHead>
<TableHead className="text-right">
{t("ui.dev.clients.consents.table.action", "Action")}
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{rows.length === 0 && !isLoading ? (
<TableRow>
<TableCell colSpan={7} className="h-24 text-center">
No consents found.
{t("msg.dev.clients.consents.empty", "No consents found.")}
</TableCell>
</TableRow>
) : (
@@ -199,11 +247,14 @@ function ClientConsentsPage() {
<TableCell>
<div className="flex items-center gap-3">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary/10 text-xs font-bold text-primary">
{(row.userName || row.subject).slice(0, 2).toUpperCase()}
{(row.userName || row.subject)
.slice(0, 2)
.toUpperCase()}
</div>
<div className="flex flex-col">
<span className="text-sm font-semibold">
{row.userName || "Subject"}
{row.userName ||
t("ui.dev.clients.consents.subject", "Subject")}
</span>
<span className="text-xs text-muted-foreground">
{row.subject}
@@ -214,7 +265,7 @@ function ClientConsentsPage() {
<TableCell>
<div className="flex flex-col">
<span className="text-sm font-semibold">
{row.tenantName || "N/A"}
{row.tenantName || t("ui.common.na", "N/A")}
</span>
<span className="text-xs text-muted-foreground">
{row.tenantId}
@@ -222,7 +273,9 @@ function ClientConsentsPage() {
</div>
</TableCell>
<TableCell>
<Badge variant="success">Active</Badge>
<Badge variant="success">
{t("ui.common.status.active", "Active")}
</Badge>
</TableCell>
<TableCell>
<div className="flex flex-wrap gap-1">
@@ -253,7 +306,7 @@ function ClientConsentsPage() {
revokeMutation.mutate({ subject: row.subject })
}
>
Revoke
{t("ui.dev.clients.consents.revoke", "Revoke")}
</Button>
</TableCell>
</TableRow>
@@ -263,15 +316,23 @@ function ClientConsentsPage() {
</Table>
<CardContent className="flex items-center justify-between border-t border-border bg-muted/10 px-6 py-4 text-sm text-muted-foreground">
<p>
Showing <span className="font-semibold text-foreground">{rows.length > 0 ? 1 : 0}</span> to{" "}
<span className="font-semibold text-foreground">{rows.length}</span> of{" "}
<span className="font-semibold text-foreground">{rows.length}</span> users
{t(
"msg.dev.clients.consents.showing",
"Showing {{from}} to {{to}} of {{total}} users",
{
from: rows.length > 0 ? 1 : 0,
to: rows.length,
total: rows.length,
},
)}
</p>
<div className="flex gap-2">
<Button variant="outline" size="icon" disabled>
<ChevronLeft className="h-4 w-4" />
</Button>
<Button size="sm" disabled={rows.length === 0}>1</Button>
<Button size="sm" disabled={rows.length === 0}>
1
</Button>
<Button variant="outline" size="icon" disabled>
<ChevronRight className="h-4 w-4" />
</Button>
@@ -283,7 +344,10 @@ function ClientConsentsPage() {
<Card className="glass-panel">
<CardHeader className="pb-2">
<p className="text-xs font-bold uppercase tracking-wider text-muted-foreground">
Active Grants
{t(
"ui.dev.clients.consents.stats.active_grants",
"Active Grants",
)}
</p>
<CardTitle className="text-2xl font-black">{rows.length}</CardTitle>
</CardHeader>
@@ -291,7 +355,10 @@ function ClientConsentsPage() {
<Card className="glass-panel">
<CardHeader className="pb-2">
<p className="text-xs font-bold uppercase tracking-wider text-muted-foreground">
Total Scopes Issued
{t(
"ui.dev.clients.consents.stats.total_scopes",
"Total Scopes Issued",
)}
</p>
<CardTitle className="text-2xl font-black">
{rows.reduce((acc, row) => acc + row.grantedScopes.length, 0)}
@@ -301,13 +368,18 @@ function ClientConsentsPage() {
<Card className="glass-panel">
<CardHeader className="pb-2">
<p className="text-xs font-bold uppercase tracking-wider text-muted-foreground">
Avg. Scopes per User
{t(
"ui.dev.clients.consents.stats.avg_scopes",
"Avg. Scopes per User",
)}
</p>
<CardTitle className="text-2xl font-black">
{rows.length > 0
? (
rows.reduce((acc, row) => acc + row.grantedScopes.length, 0) /
rows.length
rows.reduce(
(acc, row) => acc + row.grantedScopes.length,
0,
) / rows.length
).toFixed(1)
: "0.0"}
</CardTitle>