1
0
forked from baron/baron-sso

클라이언트 동의 내역 페이지 전체 목록 조회 및 UX 개선

This commit is contained in:
2026-02-06 11:24:14 +09:00
parent 7a057fa5db
commit 7f52479c5c
2 changed files with 106 additions and 99 deletions

View File

@@ -46,7 +46,7 @@ function ClientConsentsPage() {
} = useQuery({ } = useQuery({
queryKey: ["consents", clientId, subject], queryKey: ["consents", clientId, subject],
queryFn: () => fetchConsents(subject, clientId), queryFn: () => fetchConsents(subject, clientId),
enabled: subject.length > 0, enabled: clientId.length > 0, // Removed subject.length > 0 check
}); });
const revokeMutation = useMutation({ const revokeMutation = useMutation({
mutationFn: (payload: { subject: string }) => mutationFn: (payload: { subject: string }) =>
@@ -174,105 +174,109 @@ function ClientConsentsPage() {
</CardContent> </CardContent>
)} )}
{subject.length === 0 && !isLoading && !error ? ( <Table>
<div className="flex flex-col items-center justify-center py-16 text-center text-muted-foreground"> <TableHeader>
<Search className="mb-4 h-12 w-12 opacity-20" /> <TableRow>
<h3 className="mb-1 text-lg font-semibold text-foreground"> </h3> <TableHead>User</TableHead>
<p className="max-w-sm text-sm"> <TableHead>Tenant</TableHead>
.<br/> <TableHead>Status</TableHead>
ID, , . <TableHead>Granted Scopes</TableHead>
</p> <TableHead>First Granted</TableHead>
</div> <TableHead>Last Authenticated</TableHead>
) : ( <TableHead className="text-right">Action</TableHead>
<> </TableRow>
<Table> </TableHeader>
<TableHeader> <TableBody>
<TableRow> {rows.length === 0 && !isLoading ? (
<TableHead>User</TableHead> <TableRow>
<TableHead>Status</TableHead> <TableCell colSpan={7} className="h-24 text-center">
<TableHead>Granted Scopes</TableHead> No consents found.
<TableHead>Last Authenticated</TableHead> </TableCell>
<TableHead className="text-right">Action</TableHead> </TableRow>
</TableRow> ) : (
</TableHeader> rows.map((row) => (
<TableBody> <TableRow key={`${row.subject}-${row.clientId}`}>
{rows.length === 0 && !isLoading ? ( <TableCell>
<TableRow> <div className="flex items-center gap-3">
<TableCell colSpan={5} className="h-24 text-center"> <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()}
</TableCell> </div>
</TableRow> <div className="flex flex-col">
) : ( <span className="text-sm font-semibold">
rows.map((row) => ( {row.userName || "Subject"}
<TableRow key={`${row.subject}-${row.clientId}`}> </span>
<TableCell> <span className="text-xs text-muted-foreground">
<div className="flex items-center gap-3"> {row.subject}
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary/10 text-xs font-bold text-primary"> </span>
{(row.userName || row.subject).slice(0, 2).toUpperCase()} </div>
</div> </div>
<div className="flex flex-col"> </TableCell>
<span className="text-sm font-semibold"> <TableCell>
{row.userName || "Subject"} <div className="flex flex-col">
</span> <span className="text-sm font-semibold">
<span className="text-xs text-muted-foreground"> {row.tenantName || "N/A"}
{row.subject} </span>
</span> <span className="text-xs text-muted-foreground">
</div> {row.tenantId}
</div> </span>
</TableCell> </div>
<TableCell> </TableCell>
<Badge variant="success">Active</Badge> <TableCell>
</TableCell> <Badge variant="success">Active</Badge>
<TableCell> </TableCell>
<div className="flex flex-wrap gap-1"> <TableCell>
{row.grantedScopes.map((scope) => ( <div className="flex flex-wrap gap-1">
<Badge {row.grantedScopes.map((scope) => (
key={scope} <Badge
variant="muted" key={scope}
className="border bg-muted/40 text-foreground" variant="muted"
> className="border bg-muted/40 text-foreground"
{scope}
</Badge>
))}
</div>
</TableCell>
<TableCell className="text-sm text-muted-foreground">
{row.authenticatedAt || "-"}
</TableCell>
<TableCell className="text-right">
<Button
variant="ghost"
className="text-destructive"
onClick={() =>
revokeMutation.mutate({ subject: row.subject })
}
> >
Revoke {scope}
</Button> </Badge>
</TableCell> ))}
</TableRow> </div>
)) </TableCell>
)} <TableCell className="text-sm text-muted-foreground">
</TableBody> {new Date(row.createdAt).toLocaleString()}
</Table> </TableCell>
<CardContent className="flex items-center justify-between border-t border-border bg-muted/10 px-6 py-4 text-sm text-muted-foreground"> <TableCell className="text-sm text-muted-foreground">
<p> {row.authenticatedAt
Showing <span className="font-semibold text-foreground">{rows.length > 0 ? 1 : 0}</span> to{" "} ? new Date(row.authenticatedAt).toLocaleString()
<span className="font-semibold text-foreground">{rows.length}</span> of{" "} : "-"}
<span className="font-semibold text-foreground">{rows.length}</span> users </TableCell>
</p> <TableCell className="text-right">
<div className="flex gap-2"> <Button
<Button variant="outline" size="icon" disabled> variant="ghost"
<ChevronLeft className="h-4 w-4" /> className="text-destructive"
</Button> onClick={() =>
<Button size="sm" disabled={rows.length === 0}>1</Button> revokeMutation.mutate({ subject: row.subject })
<Button variant="outline" size="icon" disabled> }
<ChevronRight className="h-4 w-4" /> >
</Button> Revoke
</div> </Button>
</CardContent> </TableCell>
</> </TableRow>
)} ))
)}
</TableBody>
</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
</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 variant="outline" size="icon" disabled>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</CardContent>
</Card> </Card>
<div className="grid gap-6 md:grid-cols-3"> <div className="grid gap-6 md:grid-cols-3">

View File

@@ -54,6 +54,9 @@ export type ConsentSummary = {
clientName?: string; clientName?: string;
grantedScopes: string[]; grantedScopes: string[];
authenticatedAt?: string; authenticatedAt?: string;
createdAt: string;
tenantId?: string;
tenantName?: string;
}; };
export type ConsentListResponse = { export type ConsentListResponse = {