1
0
forked from baron/baron-sso

adminfront 로그인 해결

This commit is contained in:
2026-02-19 18:02:47 +09:00
parent ba7cca3c60
commit e40dd8120e
22 changed files with 317 additions and 214 deletions

View File

@@ -1,5 +1,6 @@
import { useQuery } from "@tanstack/react-query";
import { Building2, Plus, Users } from "lucide-react";
import { useState } from "react";
import { Link } from "react-router-dom";
import { Badge } from "../../../components/ui/badge";
import { Button } from "../../../components/ui/button";
@@ -18,8 +19,7 @@ import {
TableHeader,
TableRow,
} from "../../../components/ui/table";
import { fetchTenants, fetchGroups } from "../../../lib/adminApi";
import { useState } from "react";
import { fetchGroups, fetchTenants } from "../../../lib/adminApi";
export default function GlobalUserGroupListPage() {
const { data: tenantList, isLoading: isTenantsLoading } = useQuery({
@@ -27,7 +27,8 @@ export default function GlobalUserGroupListPage() {
queryFn: () => fetchTenants(100, 0),
});
if (isTenantsLoading) return <div className="p-8">Loading tenants and groups...</div>;
if (isTenantsLoading)
return <div className="p-8">Loading tenants and groups...</div>;
return (
<div className="space-y-8">
@@ -35,7 +36,8 @@ export default function GlobalUserGroupListPage() {
<div className="space-y-1">
<h2 className="text-3xl font-bold tracking-tight">User Groups</h2>
<p className="text-muted-foreground">
. .
.
.
</p>
</div>
</header>
@@ -62,7 +64,9 @@ function TenantGroupCard({ tenant }: { tenant: any }) {
<CardTitle className="text-xl flex items-center gap-2">
<Building2 size={20} className="text-muted-foreground" />
{tenant.name}
<Badge variant="outline" className="ml-2">{tenant.slug}</Badge>
<Badge variant="outline" className="ml-2">
{tenant.slug}
</Badge>
</CardTitle>
<CardDescription>
.
@@ -88,11 +92,16 @@ function TenantGroupCard({ tenant }: { tenant: any }) {
<TableBody>
{isLoading ? (
<TableRow>
<TableCell colSpan={4} className="text-center">Loading...</TableCell>
<TableCell colSpan={4} className="text-center">
Loading...
</TableCell>
</TableRow>
) : groups?.length === 0 ? (
<TableRow>
<TableCell colSpan={4} className="text-center text-muted-foreground py-4">
<TableCell
colSpan={4}
className="text-center text-muted-foreground py-4"
>
.
</TableCell>
</TableRow>
@@ -102,7 +111,10 @@ function TenantGroupCard({ tenant }: { tenant: any }) {
<TableCell className="font-medium">
<div className="flex items-center gap-2">
<Users size={14} className="text-primary" />
<Link to={`/tenants/${tenant.id}/user-groups/${group.id}`} className="hover:underline">
<Link
to={`/tenants/${tenant.id}/user-groups/${group.id}`}
className="hover:underline"
>
{group.name}
</Link>
</div>
@@ -111,7 +123,11 @@ function TenantGroupCard({ tenant }: { tenant: any }) {
<TableCell>{group.members?.length || 0} </TableCell>
<TableCell className="text-right">
<Button variant="ghost" size="sm" asChild>
<Link to={`/tenants/${tenant.id}/user-groups/${group.id}`}></Link>
<Link
to={`/tenants/${tenant.id}/user-groups/${group.id}`}
>
</Link>
</Button>
</TableCell>
</TableRow>

View File

@@ -61,7 +61,11 @@ export function UserGroupDetailPage() {
const [selectedRelation, setSelectedRelation] = useState("view");
// Fetch specific group details
const { data: currentGroup, isLoading: isGroupLoading, error } = useQuery({
const {
data: currentGroup,
isLoading: isGroupLoading,
error,
} = useQuery({
queryKey: ["user-group-detail", id],
queryFn: () => fetchGroup(tenantId!, id!),
enabled: !!id && !!tenantId,
@@ -112,12 +116,7 @@ export function UserGroupDetailPage() {
const assignRoleMutation = useMutation({
mutationFn: () =>
assignGroupRole(
tenantId!,
id!,
selectedTargetTenantId,
selectedRelation,
),
assignGroupRole(tenantId!, id!, selectedTargetTenantId, selectedRelation),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["user-group-roles", id] });
setIsAddRoleOpen(false);
@@ -137,29 +136,50 @@ export function UserGroupDetailPage() {
},
});
if (isGroupLoading) return (
<div className="flex items-center justify-center p-12">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
<span className="ml-3 text-muted-foreground">Loading group details...</span>
</div>
);
if (isGroupLoading)
return (
<div className="flex items-center justify-center p-12">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
<span className="ml-3 text-muted-foreground">
Loading group details...
</span>
</div>
);
if (error || !currentGroup) return (
<div className="p-8 text-center space-y-4">
<h3 className="text-xl font-semibold text-destructive">Could not load group</h3>
<div className="p-4 bg-red-50 text-red-700 rounded-md text-left text-sm font-mono overflow-auto max-w-xl mx-auto border border-red-100">
<p>Error: {(error as any)?.response?.data?.error || (error as any)?.message || "Not found"}</p>
<p className="mt-2 text-red-500 opacity-70">Path: /admin/tenants/{tenantId}/user-groups/{id}</p>
if (error || !currentGroup)
return (
<div className="p-8 text-center space-y-4">
<h3 className="text-xl font-semibold text-destructive">
Could not load group
</h3>
<div className="p-4 bg-red-50 text-red-700 rounded-md text-left text-sm font-mono overflow-auto max-w-xl mx-auto border border-red-100">
<p>
Error:{" "}
{(error as any)?.response?.data?.error ||
(error as any)?.message ||
"Not found"}
</p>
<p className="mt-2 text-red-500 opacity-70">
Path: /admin/tenants/{tenantId}/user-groups/{id}
</p>
</div>
<p className="text-muted-foreground pt-2">
The group ID might be invalid or you don't have sufficient
permissions.
</p>
<Button variant="outline" onClick={() => window.location.reload()}>
Retry
</Button>
<div className="pt-4 border-t">
<Link
to={`/tenants/${tenantId}/user-groups`}
className="text-primary hover:underline text-sm"
>
Return to Group List
</Link>
</div>
</div>
<p className="text-muted-foreground pt-2">The group ID might be invalid or you don't have sufficient permissions.</p>
<Button variant="outline" onClick={() => window.location.reload()}>Retry</Button>
<div className="pt-4 border-t">
<Link to={`/tenants/${tenantId}/user-groups`} className="text-primary hover:underline text-sm">
Return to Group List
</Link>
</div>
</div>
);
);
return (
<div className="space-y-8">
@@ -187,8 +207,8 @@ export function UserGroupDetailPage() {
</p>
</div>
<div className="flex gap-2">
<Badge variant="outline">User Group</Badge>
<Badge variant="muted">Tenant: {tenantId?.split('-')[0]}...</Badge>
<Badge variant="outline">User Group</Badge>
<Badge variant="muted">Tenant: {tenantId?.split("-")[0]}...</Badge>
</div>
</header>
@@ -217,15 +237,18 @@ export function UserGroupDetailPage() {
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label>Search User</Label>
<Input
placeholder="Search by email or name..."
<Input
placeholder="Search by email or name..."
value={searchUser}
onChange={(e) => setSearchUser(e.target.value)}
/>
</div>
<div className="space-y-2">
<Label>Select User</Label>
<Select value={selectedUserId} onValueChange={setSelectedUserId}>
<Select
value={selectedUserId}
onValueChange={setSelectedUserId}
>
<SelectTrigger>
<SelectValue placeholder="Choose a user" />
</SelectTrigger>
@@ -240,10 +263,13 @@ export function UserGroupDetailPage() {
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setIsAddMemberOpen(false)}>
<Button
variant="outline"
onClick={() => setIsAddMemberOpen(false)}
>
Cancel
</Button>
<Button
<Button
onClick={() => addMemberMutation.mutate(selectedUserId)}
disabled={!selectedUserId || addMemberMutation.isPending}
>
@@ -264,7 +290,10 @@ export function UserGroupDetailPage() {
<TableBody>
{!currentGroup.members || currentGroup.members.length === 0 ? (
<TableRow>
<TableCell colSpan={2} className="text-center py-4 text-muted-foreground">
<TableCell
colSpan={2}
className="text-center py-4 text-muted-foreground"
>
No members in this group.
</TableCell>
</TableRow>
@@ -274,13 +303,15 @@ export function UserGroupDetailPage() {
<TableCell>
<div>
<p className="font-medium">{member.name}</p>
<p className="text-xs text-muted-foreground">{member.email}</p>
<p className="text-xs text-muted-foreground">
{member.email}
</p>
</div>
</TableCell>
<TableCell className="text-right">
<Button
variant="ghost"
size="icon"
<Button
variant="ghost"
size="icon"
className="text-destructive"
onClick={() => removeMemberMutation.mutate(member.id)}
>
@@ -300,7 +331,9 @@ export function UserGroupDetailPage() {
<CardHeader className="flex flex-row items-center justify-between">
<div>
<CardTitle>Permissions</CardTitle>
<CardDescription>Tenant roles assigned to this group.</CardDescription>
<CardDescription>
Tenant roles assigned to this group.
</CardDescription>
</div>
<Dialog open={isAddRoleOpen} onOpenChange={setIsAddRoleOpen}>
<DialogTrigger asChild>
@@ -313,13 +346,17 @@ export function UserGroupDetailPage() {
<DialogHeader>
<DialogTitle>Assign Tenant Role</DialogTitle>
<DialogDescription>
Members of this group will inherit this role on the target tenant.
Members of this group will inherit this role on the target
tenant.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<div className="space-y-2">
<Label>Target Tenant</Label>
<Select value={selectedTargetTenantId} onValueChange={setSelectedTargetTenantId}>
<Select
value={selectedTargetTenantId}
onValueChange={setSelectedTargetTenantId}
>
<SelectTrigger>
<SelectValue placeholder="Select target tenant" />
</SelectTrigger>
@@ -334,25 +371,37 @@ export function UserGroupDetailPage() {
</div>
<div className="space-y-2">
<Label>Role (Relation)</Label>
<Select value={selectedRelation} onValueChange={setSelectedRelation}>
<Select
value={selectedRelation}
onValueChange={setSelectedRelation}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="view">View (Read-only)</SelectItem>
<SelectItem value="manage">Manage (Read/Write)</SelectItem>
<SelectItem value="admins">Admin (Full Control)</SelectItem>
<SelectItem value="manage">
Manage (Read/Write)
</SelectItem>
<SelectItem value="admins">
Admin (Full Control)
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setIsAddRoleOpen(false)}>
<Button
variant="outline"
onClick={() => setIsAddRoleOpen(false)}
>
Cancel
</Button>
<Button
<Button
onClick={() => assignRoleMutation.mutate()}
disabled={!selectedTargetTenantId || assignRoleMutation.isPending}
disabled={
!selectedTargetTenantId || assignRoleMutation.isPending
}
>
Assign
</Button>
@@ -371,10 +420,17 @@ export function UserGroupDetailPage() {
</TableHeader>
<TableBody>
{isRolesLoading ? (
<TableRow><TableCell colSpan={3} className="text-center">Loading...</TableCell></TableRow>
<TableRow>
<TableCell colSpan={3} className="text-center">
Loading...
</TableCell>
</TableRow>
) : !groupRoles || groupRoles.length === 0 ? (
<TableRow>
<TableCell colSpan={3} className="text-center py-4 text-muted-foreground">
<TableCell
colSpan={3}
className="text-center py-4 text-muted-foreground"
>
No roles assigned.
</TableCell>
</TableRow>
@@ -382,17 +438,26 @@ export function UserGroupDetailPage() {
groupRoles.map((role, idx) => (
<TableRow key={`${role.tenantId}-${role.relation}-${idx}`}>
<TableCell>
<div className="font-medium">{role.tenantName || role.tenantId}</div>
<div className="font-medium">
{role.tenantName || role.tenantId}
</div>
</TableCell>
<TableCell>
<Badge variant="outline" className="capitalize">{role.relation}</Badge>
<Badge variant="outline" className="capitalize">
{role.relation}
</Badge>
</TableCell>
<TableCell className="text-right">
<Button
variant="ghost"
size="icon"
<Button
variant="ghost"
size="icon"
className="text-destructive"
onClick={() => removeRoleMutation.mutate({ targetTenantId: role.tenantId, relation: role.relation })}
onClick={() =>
removeRoleMutation.mutate({
targetTenantId: role.tenantId,
relation: role.relation,
})
}
>
<Trash2 size={14} />
</Button>