forked from baron/baron-sso
devfront consents 및 audit 테이블 공통화
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import type { ReactNode, ThHTMLAttributes } from "react";
|
||||
import type { SortConfig } from "../../utils";
|
||||
import { commonTableHeadClass } from "../../../ui/table";
|
||||
|
||||
export const sortableTableHeadBaseClassName =
|
||||
"h-12 px-6 text-left text-xs font-sans font-bold uppercase tracking-[0.08em] text-foreground align-middle";
|
||||
commonTableHeadClass;
|
||||
|
||||
export const sortableTableHeaderClassName =
|
||||
"sticky top-0 z-10 bg-secondary shadow-sm";
|
||||
|
||||
@@ -6,7 +6,7 @@ export const commonTableFooterClass = "bg-muted/50 font-medium text-foreground";
|
||||
export const commonTableRowClass =
|
||||
"border-b transition-colors hover:bg-muted/30 data-[state=selected]:bg-muted";
|
||||
export const commonTableHeadClass =
|
||||
"h-12 px-6 text-left text-xs font-bold uppercase tracking-[0.08em] text-muted-foreground align-middle";
|
||||
"h-12 px-6 text-left text-xs font-sans font-bold uppercase tracking-[0.08em] text-foreground align-middle";
|
||||
export const commonTableCellClass = "p-6 align-middle text-sm";
|
||||
export const commonTableCaptionClass = "mt-4 text-sm text-muted-foreground";
|
||||
export const commonTableShellClass =
|
||||
|
||||
@@ -28,6 +28,10 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "../../components/ui/table";
|
||||
import {
|
||||
commonTableShellClass,
|
||||
commonTableViewportClass,
|
||||
} from "../../../../common/ui/table";
|
||||
import { fetchClient, fetchConsents, revokeConsent } from "../../lib/devApi";
|
||||
import { t } from "../../lib/i18n";
|
||||
import { cn } from "../../lib/utils";
|
||||
@@ -430,146 +434,153 @@ function ClientConsentsPage() {
|
||||
</CardContent>
|
||||
)}
|
||||
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<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 / Revoked",
|
||||
)}
|
||||
</TableHead>
|
||||
<TableHead className="text-right">
|
||||
{t("ui.dev.clients.consents.table.action", "Action")}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredRows.length === 0 && !isLoading && !error ? (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={7}
|
||||
className="h-32 text-center text-muted-foreground"
|
||||
>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Search className="h-8 w-8 opacity-20" />
|
||||
<p>
|
||||
{t(
|
||||
"msg.dev.clients.consents.empty",
|
||||
"No consents found.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
filteredRows.map((row) => (
|
||||
<TableRow
|
||||
key={`${row.subject}-${row.clientId}`}
|
||||
className={row.status === "revoked" ? "opacity-60" : ""}
|
||||
>
|
||||
<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()}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-semibold">
|
||||
{row.userName ||
|
||||
t("ui.dev.clients.consents.subject", "Subject")}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{row.subject}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-semibold">
|
||||
{row.tenantName || t("ui.common.na", "N/A")}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{row.tenantId}
|
||||
</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{row.status === "active" ? (
|
||||
<Badge variant="success">
|
||||
{t("ui.common.status.active", "Active")}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="warning">
|
||||
{t("ui.dev.clients.consents.status_revoked", "Revoked")}
|
||||
</Badge>
|
||||
<div className={commonTableShellClass}>
|
||||
<div className={commonTableViewportClass}>
|
||||
<Table>
|
||||
<TableHeader className="sticky top-0 z-10 bg-secondary shadow-sm">
|
||||
<TableRow>
|
||||
<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",
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{row.grantedScopes.map((scope) => (
|
||||
<Badge
|
||||
key={scope}
|
||||
variant="muted"
|
||||
className="border bg-muted/40 text-foreground"
|
||||
>
|
||||
{scope}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">
|
||||
{new Date(row.createdAt).toLocaleString()}
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">
|
||||
{row.status === "revoked" && row.deletedAt ? (
|
||||
<span className="text-destructive font-medium">
|
||||
{t("ui.dev.clients.consents.revoked_at", "Revoked: ")}
|
||||
{new Date(row.deletedAt).toLocaleString()}
|
||||
</span>
|
||||
) : row.authenticatedAt ? (
|
||||
new Date(row.authenticatedAt).toLocaleString()
|
||||
) : (
|
||||
"-"
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
{t(
|
||||
"ui.dev.clients.consents.table.last_auth",
|
||||
"Last Authenticated / Revoked",
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{row.status === "active" && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-destructive hover:bg-destructive/10"
|
||||
onClick={() => handleRevoke(row.subject)}
|
||||
disabled={revokeMutation.isPending}
|
||||
>
|
||||
{t("ui.dev.clients.consents.revoke", "Revoke")}
|
||||
</Button>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableHead>
|
||||
<TableHead className="text-right">
|
||||
{t("ui.dev.clients.consents.table.action", "Action")}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredRows.length === 0 && !isLoading && !error ? (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={7}
|
||||
className="h-32 text-center text-muted-foreground"
|
||||
>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Search className="h-8 w-8 opacity-20" />
|
||||
<p>
|
||||
{t(
|
||||
"msg.dev.clients.consents.empty",
|
||||
"No consents found.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
filteredRows.map((row) => (
|
||||
<TableRow
|
||||
key={`${row.subject}-${row.clientId}`}
|
||||
className={row.status === "revoked" ? "opacity-60" : ""}
|
||||
>
|
||||
<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()}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-semibold">
|
||||
{row.userName ||
|
||||
t("ui.dev.clients.consents.subject", "Subject")}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{row.subject}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-semibold">
|
||||
{row.tenantName || t("ui.common.na", "N/A")}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{row.tenantId}
|
||||
</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{row.status === "active" ? (
|
||||
<Badge variant="success">
|
||||
{t("ui.common.status.active", "Active")}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="warning">
|
||||
{t(
|
||||
"ui.dev.clients.consents.status_revoked",
|
||||
"Revoked",
|
||||
)}
|
||||
</Badge>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{row.grantedScopes.map((scope) => (
|
||||
<Badge
|
||||
key={scope}
|
||||
variant="muted"
|
||||
className="border bg-muted/40 text-foreground"
|
||||
>
|
||||
{scope}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">
|
||||
{new Date(row.createdAt).toLocaleString()}
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">
|
||||
{row.status === "revoked" && row.deletedAt ? (
|
||||
<span className="text-destructive font-medium">
|
||||
{t("ui.dev.clients.consents.revoked_at", "Revoked: ")}
|
||||
{new Date(row.deletedAt).toLocaleString()}
|
||||
</span>
|
||||
) : row.authenticatedAt ? (
|
||||
new Date(row.authenticatedAt).toLocaleString()
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{row.status === "active" && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-destructive hover:bg-destructive/10"
|
||||
onClick={() => handleRevoke(row.subject)}
|
||||
disabled={revokeMutation.isPending}
|
||||
>
|
||||
{t("ui.dev.clients.consents.revoke", "Revoke")}
|
||||
</Button>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
<CardContent className="flex items-center justify-between border-t border-border bg-muted/10 px-6 py-4 text-sm text-muted-foreground">
|
||||
<p>
|
||||
{t(
|
||||
|
||||
Reference in New Issue
Block a user