diff --git a/packages/admin/.tanstack/tmp/ea12e527-f0abc49f852aebe0077e70caa4e1d479 b/packages/admin/.tanstack/tmp/57794a11-f0abc49f852aebe0077e70caa4e1d479 similarity index 82% rename from packages/admin/.tanstack/tmp/ea12e527-f0abc49f852aebe0077e70caa4e1d479 rename to packages/admin/.tanstack/tmp/57794a11-f0abc49f852aebe0077e70caa4e1d479 index 62ce823..ed26da4 100644 --- a/packages/admin/.tanstack/tmp/ea12e527-f0abc49f852aebe0077e70caa4e1d479 +++ b/packages/admin/.tanstack/tmp/57794a11-f0abc49f852aebe0077e70caa4e1d479 @@ -12,9 +12,13 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as LoginRouteImport } from './routes/login' import { Route as AuthenticatedRouteImport } from './routes/_authenticated' import { Route as AuthenticatedIndexRouteImport } from './routes/_authenticated/index' +import { Route as AuthenticatedUsersRouteImport } from './routes/_authenticated/users' import { Route as AuthenticatedHelpRouteImport } from './routes/_authenticated/help' +import { Route as AuthenticatedRolesIndexRouteImport } from './routes/_authenticated/roles/index' import { Route as AuthenticatedMembersIndexRouteImport } from './routes/_authenticated/members/index' import { Route as AuthenticatedAccountsIndexRouteImport } from './routes/_authenticated/accounts/index' +import { Route as AuthenticatedRolesNewRouteImport } from './routes/_authenticated/roles/new' +import { Route as AuthenticatedRolesRoleIdRouteImport } from './routes/_authenticated/roles/$roleId' import { Route as AuthenticatedMembersMemberIdRouteImport } from './routes/_authenticated/members/$memberId' import { Route as AuthenticatedAccountsNewRouteImport } from './routes/_authenticated/accounts/new' import { Route as AuthenticatedAccountsAccountIdRouteImport } from './routes/_authenticated/accounts/$accountId' @@ -38,11 +42,21 @@ const AuthenticatedIndexRoute = AuthenticatedIndexRouteImport.update({ path: '/', getParentRoute: () => AuthenticatedRoute, } as any) +const AuthenticatedUsersRoute = AuthenticatedUsersRouteImport.update({ + id: '/users', + path: '/users', + getParentRoute: () => AuthenticatedRoute, +} as any) const AuthenticatedHelpRoute = AuthenticatedHelpRouteImport.update({ id: '/help', path: '/help', getParentRoute: () => AuthenticatedRoute, } as any) +const AuthenticatedRolesIndexRoute = AuthenticatedRolesIndexRouteImport.update({ + id: '/roles/', + path: '/roles/', + getParentRoute: () => AuthenticatedRoute, +} as any) const AuthenticatedMembersIndexRoute = AuthenticatedMembersIndexRouteImport.update({ id: '/members/', @@ -55,6 +69,17 @@ const AuthenticatedAccountsIndexRoute = path: '/accounts/', getParentRoute: () => AuthenticatedRoute, } as any) +const AuthenticatedRolesNewRoute = AuthenticatedRolesNewRouteImport.update({ + id: '/roles/new', + path: '/roles/new', + getParentRoute: () => AuthenticatedRoute, +} as any) +const AuthenticatedRolesRoleIdRoute = + AuthenticatedRolesRoleIdRouteImport.update({ + id: '/roles/$roleId', + path: '/roles/$roleId', + getParentRoute: () => AuthenticatedRoute, + } as any) const AuthenticatedMembersMemberIdRoute = AuthenticatedMembersMemberIdRouteImport.update({ id: '/members/$memberId', @@ -108,11 +133,15 @@ export interface FileRoutesByFullPath { '/': typeof AuthenticatedIndexRoute '/login': typeof LoginRoute '/help': typeof AuthenticatedHelpRoute + '/users': typeof AuthenticatedUsersRoute '/accounts/$accountId': typeof AuthenticatedAccountsAccountIdRouteWithChildren '/accounts/new': typeof AuthenticatedAccountsNewRoute '/members/$memberId': typeof AuthenticatedMembersMemberIdRoute + '/roles/$roleId': typeof AuthenticatedRolesRoleIdRoute + '/roles/new': typeof AuthenticatedRolesNewRoute '/accounts/': typeof AuthenticatedAccountsIndexRoute '/members/': typeof AuthenticatedMembersIndexRoute + '/roles/': typeof AuthenticatedRolesIndexRoute '/accounts/$accountId/members': typeof AuthenticatedAccountsAccountIdMembersRoute '/accounts/$accountId/payment-methods': typeof AuthenticatedAccountsAccountIdPaymentMethodsRoute '/accounts/$accountId/processor-links': typeof AuthenticatedAccountsAccountIdProcessorLinksRoute @@ -122,11 +151,15 @@ export interface FileRoutesByFullPath { export interface FileRoutesByTo { '/login': typeof LoginRoute '/help': typeof AuthenticatedHelpRoute + '/users': typeof AuthenticatedUsersRoute '/': typeof AuthenticatedIndexRoute '/accounts/new': typeof AuthenticatedAccountsNewRoute '/members/$memberId': typeof AuthenticatedMembersMemberIdRoute + '/roles/$roleId': typeof AuthenticatedRolesRoleIdRoute + '/roles/new': typeof AuthenticatedRolesNewRoute '/accounts': typeof AuthenticatedAccountsIndexRoute '/members': typeof AuthenticatedMembersIndexRoute + '/roles': typeof AuthenticatedRolesIndexRoute '/accounts/$accountId/members': typeof AuthenticatedAccountsAccountIdMembersRoute '/accounts/$accountId/payment-methods': typeof AuthenticatedAccountsAccountIdPaymentMethodsRoute '/accounts/$accountId/processor-links': typeof AuthenticatedAccountsAccountIdProcessorLinksRoute @@ -138,12 +171,16 @@ export interface FileRoutesById { '/_authenticated': typeof AuthenticatedRouteWithChildren '/login': typeof LoginRoute '/_authenticated/help': typeof AuthenticatedHelpRoute + '/_authenticated/users': typeof AuthenticatedUsersRoute '/_authenticated/': typeof AuthenticatedIndexRoute '/_authenticated/accounts/$accountId': typeof AuthenticatedAccountsAccountIdRouteWithChildren '/_authenticated/accounts/new': typeof AuthenticatedAccountsNewRoute '/_authenticated/members/$memberId': typeof AuthenticatedMembersMemberIdRoute + '/_authenticated/roles/$roleId': typeof AuthenticatedRolesRoleIdRoute + '/_authenticated/roles/new': typeof AuthenticatedRolesNewRoute '/_authenticated/accounts/': typeof AuthenticatedAccountsIndexRoute '/_authenticated/members/': typeof AuthenticatedMembersIndexRoute + '/_authenticated/roles/': typeof AuthenticatedRolesIndexRoute '/_authenticated/accounts/$accountId/members': typeof AuthenticatedAccountsAccountIdMembersRoute '/_authenticated/accounts/$accountId/payment-methods': typeof AuthenticatedAccountsAccountIdPaymentMethodsRoute '/_authenticated/accounts/$accountId/processor-links': typeof AuthenticatedAccountsAccountIdProcessorLinksRoute @@ -156,11 +193,15 @@ export interface FileRouteTypes { | '/' | '/login' | '/help' + | '/users' | '/accounts/$accountId' | '/accounts/new' | '/members/$memberId' + | '/roles/$roleId' + | '/roles/new' | '/accounts/' | '/members/' + | '/roles/' | '/accounts/$accountId/members' | '/accounts/$accountId/payment-methods' | '/accounts/$accountId/processor-links' @@ -170,11 +211,15 @@ export interface FileRouteTypes { to: | '/login' | '/help' + | '/users' | '/' | '/accounts/new' | '/members/$memberId' + | '/roles/$roleId' + | '/roles/new' | '/accounts' | '/members' + | '/roles' | '/accounts/$accountId/members' | '/accounts/$accountId/payment-methods' | '/accounts/$accountId/processor-links' @@ -185,12 +230,16 @@ export interface FileRouteTypes { | '/_authenticated' | '/login' | '/_authenticated/help' + | '/_authenticated/users' | '/_authenticated/' | '/_authenticated/accounts/$accountId' | '/_authenticated/accounts/new' | '/_authenticated/members/$memberId' + | '/_authenticated/roles/$roleId' + | '/_authenticated/roles/new' | '/_authenticated/accounts/' | '/_authenticated/members/' + | '/_authenticated/roles/' | '/_authenticated/accounts/$accountId/members' | '/_authenticated/accounts/$accountId/payment-methods' | '/_authenticated/accounts/$accountId/processor-links' @@ -226,6 +275,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticatedIndexRouteImport parentRoute: typeof AuthenticatedRoute } + '/_authenticated/users': { + id: '/_authenticated/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof AuthenticatedUsersRouteImport + parentRoute: typeof AuthenticatedRoute + } '/_authenticated/help': { id: '/_authenticated/help' path: '/help' @@ -233,6 +289,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticatedHelpRouteImport parentRoute: typeof AuthenticatedRoute } + '/_authenticated/roles/': { + id: '/_authenticated/roles/' + path: '/roles' + fullPath: '/roles/' + preLoaderRoute: typeof AuthenticatedRolesIndexRouteImport + parentRoute: typeof AuthenticatedRoute + } '/_authenticated/members/': { id: '/_authenticated/members/' path: '/members' @@ -247,6 +310,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticatedAccountsIndexRouteImport parentRoute: typeof AuthenticatedRoute } + '/_authenticated/roles/new': { + id: '/_authenticated/roles/new' + path: '/roles/new' + fullPath: '/roles/new' + preLoaderRoute: typeof AuthenticatedRolesNewRouteImport + parentRoute: typeof AuthenticatedRoute + } + '/_authenticated/roles/$roleId': { + id: '/_authenticated/roles/$roleId' + path: '/roles/$roleId' + fullPath: '/roles/$roleId' + preLoaderRoute: typeof AuthenticatedRolesRoleIdRouteImport + parentRoute: typeof AuthenticatedRoute + } '/_authenticated/members/$memberId': { id: '/_authenticated/members/$memberId' path: '/members/$memberId' @@ -335,23 +412,31 @@ const AuthenticatedAccountsAccountIdRouteWithChildren = interface AuthenticatedRouteChildren { AuthenticatedHelpRoute: typeof AuthenticatedHelpRoute + AuthenticatedUsersRoute: typeof AuthenticatedUsersRoute AuthenticatedIndexRoute: typeof AuthenticatedIndexRoute AuthenticatedAccountsAccountIdRoute: typeof AuthenticatedAccountsAccountIdRouteWithChildren AuthenticatedAccountsNewRoute: typeof AuthenticatedAccountsNewRoute AuthenticatedMembersMemberIdRoute: typeof AuthenticatedMembersMemberIdRoute + AuthenticatedRolesRoleIdRoute: typeof AuthenticatedRolesRoleIdRoute + AuthenticatedRolesNewRoute: typeof AuthenticatedRolesNewRoute AuthenticatedAccountsIndexRoute: typeof AuthenticatedAccountsIndexRoute AuthenticatedMembersIndexRoute: typeof AuthenticatedMembersIndexRoute + AuthenticatedRolesIndexRoute: typeof AuthenticatedRolesIndexRoute } const AuthenticatedRouteChildren: AuthenticatedRouteChildren = { AuthenticatedHelpRoute: AuthenticatedHelpRoute, + AuthenticatedUsersRoute: AuthenticatedUsersRoute, AuthenticatedIndexRoute: AuthenticatedIndexRoute, AuthenticatedAccountsAccountIdRoute: AuthenticatedAccountsAccountIdRouteWithChildren, AuthenticatedAccountsNewRoute: AuthenticatedAccountsNewRoute, AuthenticatedMembersMemberIdRoute: AuthenticatedMembersMemberIdRoute, + AuthenticatedRolesRoleIdRoute: AuthenticatedRolesRoleIdRoute, + AuthenticatedRolesNewRoute: AuthenticatedRolesNewRoute, AuthenticatedAccountsIndexRoute: AuthenticatedAccountsIndexRoute, AuthenticatedMembersIndexRoute: AuthenticatedMembersIndexRoute, + AuthenticatedRolesIndexRoute: AuthenticatedRolesIndexRoute, } const AuthenticatedRouteWithChildren = AuthenticatedRoute._addFileChildren( diff --git a/packages/admin/src/api/rbac.ts b/packages/admin/src/api/rbac.ts new file mode 100644 index 0000000..a47d52c --- /dev/null +++ b/packages/admin/src/api/rbac.ts @@ -0,0 +1,56 @@ +import { queryOptions } from '@tanstack/react-query' +import { api } from '@/lib/api-client' +import type { Permission, Role } from '@/types/rbac' + +export const rbacKeys = { + permissions: ['permissions'] as const, + roles: ['roles'] as const, + role: (id: string) => ['roles', id] as const, + userRoles: (userId: string) => ['users', userId, 'roles'] as const, + myPermissions: ['me', 'permissions'] as const, +} + +export function permissionListOptions() { + return queryOptions({ + queryKey: rbacKeys.permissions, + queryFn: () => api.get<{ data: Permission[] }>('/v1/permissions'), + }) +} + +export function roleListOptions() { + return queryOptions({ + queryKey: rbacKeys.roles, + queryFn: () => api.get<{ data: Role[] }>('/v1/roles'), + }) +} + +export function roleDetailOptions(id: string) { + return queryOptions({ + queryKey: rbacKeys.role(id), + queryFn: () => api.get(`/v1/roles/${id}`), + }) +} + +export function myPermissionsOptions() { + return queryOptions({ + queryKey: rbacKeys.myPermissions, + queryFn: () => api.get<{ permissions: string[]; roles: { id: string; name: string; slug: string }[] }>('/v1/me/permissions'), + }) +} + +export const rbacMutations = { + createRole: (data: Record) => + api.post('/v1/roles', data), + + updateRole: (id: string, data: Record) => + api.patch(`/v1/roles/${id}`, data), + + deleteRole: (id: string) => + api.del(`/v1/roles/${id}`), + + assignRole: (userId: string, roleId: string) => + api.post(`/v1/users/${userId}/roles`, { roleId }), + + removeRole: (userId: string, roleId: string) => + api.del(`/v1/users/${userId}/roles/${roleId}`), +} diff --git a/packages/admin/src/api/users.ts b/packages/admin/src/api/users.ts new file mode 100644 index 0000000..b13ffc4 --- /dev/null +++ b/packages/admin/src/api/users.ts @@ -0,0 +1,22 @@ +import { queryOptions } from '@tanstack/react-query' +import { api } from '@/lib/api-client' + +export interface UserRecord { + id: string + email: string + firstName: string + lastName: string + role: string + createdAt: string +} + +export const userKeys = { + roles: (userId: string) => ['users', userId, 'roles'] as const, +} + +export function userRolesOptions(userId: string) { + return queryOptions({ + queryKey: userKeys.roles(userId), + queryFn: () => api.get<{ data: { id: string; name: string; slug: string; isSystem: boolean }[] }>(`/v1/users/${userId}/roles`), + }) +} diff --git a/packages/admin/src/routeTree.gen.ts b/packages/admin/src/routeTree.gen.ts index 62ce823..ed26da4 100644 --- a/packages/admin/src/routeTree.gen.ts +++ b/packages/admin/src/routeTree.gen.ts @@ -12,9 +12,13 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as LoginRouteImport } from './routes/login' import { Route as AuthenticatedRouteImport } from './routes/_authenticated' import { Route as AuthenticatedIndexRouteImport } from './routes/_authenticated/index' +import { Route as AuthenticatedUsersRouteImport } from './routes/_authenticated/users' import { Route as AuthenticatedHelpRouteImport } from './routes/_authenticated/help' +import { Route as AuthenticatedRolesIndexRouteImport } from './routes/_authenticated/roles/index' import { Route as AuthenticatedMembersIndexRouteImport } from './routes/_authenticated/members/index' import { Route as AuthenticatedAccountsIndexRouteImport } from './routes/_authenticated/accounts/index' +import { Route as AuthenticatedRolesNewRouteImport } from './routes/_authenticated/roles/new' +import { Route as AuthenticatedRolesRoleIdRouteImport } from './routes/_authenticated/roles/$roleId' import { Route as AuthenticatedMembersMemberIdRouteImport } from './routes/_authenticated/members/$memberId' import { Route as AuthenticatedAccountsNewRouteImport } from './routes/_authenticated/accounts/new' import { Route as AuthenticatedAccountsAccountIdRouteImport } from './routes/_authenticated/accounts/$accountId' @@ -38,11 +42,21 @@ const AuthenticatedIndexRoute = AuthenticatedIndexRouteImport.update({ path: '/', getParentRoute: () => AuthenticatedRoute, } as any) +const AuthenticatedUsersRoute = AuthenticatedUsersRouteImport.update({ + id: '/users', + path: '/users', + getParentRoute: () => AuthenticatedRoute, +} as any) const AuthenticatedHelpRoute = AuthenticatedHelpRouteImport.update({ id: '/help', path: '/help', getParentRoute: () => AuthenticatedRoute, } as any) +const AuthenticatedRolesIndexRoute = AuthenticatedRolesIndexRouteImport.update({ + id: '/roles/', + path: '/roles/', + getParentRoute: () => AuthenticatedRoute, +} as any) const AuthenticatedMembersIndexRoute = AuthenticatedMembersIndexRouteImport.update({ id: '/members/', @@ -55,6 +69,17 @@ const AuthenticatedAccountsIndexRoute = path: '/accounts/', getParentRoute: () => AuthenticatedRoute, } as any) +const AuthenticatedRolesNewRoute = AuthenticatedRolesNewRouteImport.update({ + id: '/roles/new', + path: '/roles/new', + getParentRoute: () => AuthenticatedRoute, +} as any) +const AuthenticatedRolesRoleIdRoute = + AuthenticatedRolesRoleIdRouteImport.update({ + id: '/roles/$roleId', + path: '/roles/$roleId', + getParentRoute: () => AuthenticatedRoute, + } as any) const AuthenticatedMembersMemberIdRoute = AuthenticatedMembersMemberIdRouteImport.update({ id: '/members/$memberId', @@ -108,11 +133,15 @@ export interface FileRoutesByFullPath { '/': typeof AuthenticatedIndexRoute '/login': typeof LoginRoute '/help': typeof AuthenticatedHelpRoute + '/users': typeof AuthenticatedUsersRoute '/accounts/$accountId': typeof AuthenticatedAccountsAccountIdRouteWithChildren '/accounts/new': typeof AuthenticatedAccountsNewRoute '/members/$memberId': typeof AuthenticatedMembersMemberIdRoute + '/roles/$roleId': typeof AuthenticatedRolesRoleIdRoute + '/roles/new': typeof AuthenticatedRolesNewRoute '/accounts/': typeof AuthenticatedAccountsIndexRoute '/members/': typeof AuthenticatedMembersIndexRoute + '/roles/': typeof AuthenticatedRolesIndexRoute '/accounts/$accountId/members': typeof AuthenticatedAccountsAccountIdMembersRoute '/accounts/$accountId/payment-methods': typeof AuthenticatedAccountsAccountIdPaymentMethodsRoute '/accounts/$accountId/processor-links': typeof AuthenticatedAccountsAccountIdProcessorLinksRoute @@ -122,11 +151,15 @@ export interface FileRoutesByFullPath { export interface FileRoutesByTo { '/login': typeof LoginRoute '/help': typeof AuthenticatedHelpRoute + '/users': typeof AuthenticatedUsersRoute '/': typeof AuthenticatedIndexRoute '/accounts/new': typeof AuthenticatedAccountsNewRoute '/members/$memberId': typeof AuthenticatedMembersMemberIdRoute + '/roles/$roleId': typeof AuthenticatedRolesRoleIdRoute + '/roles/new': typeof AuthenticatedRolesNewRoute '/accounts': typeof AuthenticatedAccountsIndexRoute '/members': typeof AuthenticatedMembersIndexRoute + '/roles': typeof AuthenticatedRolesIndexRoute '/accounts/$accountId/members': typeof AuthenticatedAccountsAccountIdMembersRoute '/accounts/$accountId/payment-methods': typeof AuthenticatedAccountsAccountIdPaymentMethodsRoute '/accounts/$accountId/processor-links': typeof AuthenticatedAccountsAccountIdProcessorLinksRoute @@ -138,12 +171,16 @@ export interface FileRoutesById { '/_authenticated': typeof AuthenticatedRouteWithChildren '/login': typeof LoginRoute '/_authenticated/help': typeof AuthenticatedHelpRoute + '/_authenticated/users': typeof AuthenticatedUsersRoute '/_authenticated/': typeof AuthenticatedIndexRoute '/_authenticated/accounts/$accountId': typeof AuthenticatedAccountsAccountIdRouteWithChildren '/_authenticated/accounts/new': typeof AuthenticatedAccountsNewRoute '/_authenticated/members/$memberId': typeof AuthenticatedMembersMemberIdRoute + '/_authenticated/roles/$roleId': typeof AuthenticatedRolesRoleIdRoute + '/_authenticated/roles/new': typeof AuthenticatedRolesNewRoute '/_authenticated/accounts/': typeof AuthenticatedAccountsIndexRoute '/_authenticated/members/': typeof AuthenticatedMembersIndexRoute + '/_authenticated/roles/': typeof AuthenticatedRolesIndexRoute '/_authenticated/accounts/$accountId/members': typeof AuthenticatedAccountsAccountIdMembersRoute '/_authenticated/accounts/$accountId/payment-methods': typeof AuthenticatedAccountsAccountIdPaymentMethodsRoute '/_authenticated/accounts/$accountId/processor-links': typeof AuthenticatedAccountsAccountIdProcessorLinksRoute @@ -156,11 +193,15 @@ export interface FileRouteTypes { | '/' | '/login' | '/help' + | '/users' | '/accounts/$accountId' | '/accounts/new' | '/members/$memberId' + | '/roles/$roleId' + | '/roles/new' | '/accounts/' | '/members/' + | '/roles/' | '/accounts/$accountId/members' | '/accounts/$accountId/payment-methods' | '/accounts/$accountId/processor-links' @@ -170,11 +211,15 @@ export interface FileRouteTypes { to: | '/login' | '/help' + | '/users' | '/' | '/accounts/new' | '/members/$memberId' + | '/roles/$roleId' + | '/roles/new' | '/accounts' | '/members' + | '/roles' | '/accounts/$accountId/members' | '/accounts/$accountId/payment-methods' | '/accounts/$accountId/processor-links' @@ -185,12 +230,16 @@ export interface FileRouteTypes { | '/_authenticated' | '/login' | '/_authenticated/help' + | '/_authenticated/users' | '/_authenticated/' | '/_authenticated/accounts/$accountId' | '/_authenticated/accounts/new' | '/_authenticated/members/$memberId' + | '/_authenticated/roles/$roleId' + | '/_authenticated/roles/new' | '/_authenticated/accounts/' | '/_authenticated/members/' + | '/_authenticated/roles/' | '/_authenticated/accounts/$accountId/members' | '/_authenticated/accounts/$accountId/payment-methods' | '/_authenticated/accounts/$accountId/processor-links' @@ -226,6 +275,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticatedIndexRouteImport parentRoute: typeof AuthenticatedRoute } + '/_authenticated/users': { + id: '/_authenticated/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof AuthenticatedUsersRouteImport + parentRoute: typeof AuthenticatedRoute + } '/_authenticated/help': { id: '/_authenticated/help' path: '/help' @@ -233,6 +289,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticatedHelpRouteImport parentRoute: typeof AuthenticatedRoute } + '/_authenticated/roles/': { + id: '/_authenticated/roles/' + path: '/roles' + fullPath: '/roles/' + preLoaderRoute: typeof AuthenticatedRolesIndexRouteImport + parentRoute: typeof AuthenticatedRoute + } '/_authenticated/members/': { id: '/_authenticated/members/' path: '/members' @@ -247,6 +310,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticatedAccountsIndexRouteImport parentRoute: typeof AuthenticatedRoute } + '/_authenticated/roles/new': { + id: '/_authenticated/roles/new' + path: '/roles/new' + fullPath: '/roles/new' + preLoaderRoute: typeof AuthenticatedRolesNewRouteImport + parentRoute: typeof AuthenticatedRoute + } + '/_authenticated/roles/$roleId': { + id: '/_authenticated/roles/$roleId' + path: '/roles/$roleId' + fullPath: '/roles/$roleId' + preLoaderRoute: typeof AuthenticatedRolesRoleIdRouteImport + parentRoute: typeof AuthenticatedRoute + } '/_authenticated/members/$memberId': { id: '/_authenticated/members/$memberId' path: '/members/$memberId' @@ -335,23 +412,31 @@ const AuthenticatedAccountsAccountIdRouteWithChildren = interface AuthenticatedRouteChildren { AuthenticatedHelpRoute: typeof AuthenticatedHelpRoute + AuthenticatedUsersRoute: typeof AuthenticatedUsersRoute AuthenticatedIndexRoute: typeof AuthenticatedIndexRoute AuthenticatedAccountsAccountIdRoute: typeof AuthenticatedAccountsAccountIdRouteWithChildren AuthenticatedAccountsNewRoute: typeof AuthenticatedAccountsNewRoute AuthenticatedMembersMemberIdRoute: typeof AuthenticatedMembersMemberIdRoute + AuthenticatedRolesRoleIdRoute: typeof AuthenticatedRolesRoleIdRoute + AuthenticatedRolesNewRoute: typeof AuthenticatedRolesNewRoute AuthenticatedAccountsIndexRoute: typeof AuthenticatedAccountsIndexRoute AuthenticatedMembersIndexRoute: typeof AuthenticatedMembersIndexRoute + AuthenticatedRolesIndexRoute: typeof AuthenticatedRolesIndexRoute } const AuthenticatedRouteChildren: AuthenticatedRouteChildren = { AuthenticatedHelpRoute: AuthenticatedHelpRoute, + AuthenticatedUsersRoute: AuthenticatedUsersRoute, AuthenticatedIndexRoute: AuthenticatedIndexRoute, AuthenticatedAccountsAccountIdRoute: AuthenticatedAccountsAccountIdRouteWithChildren, AuthenticatedAccountsNewRoute: AuthenticatedAccountsNewRoute, AuthenticatedMembersMemberIdRoute: AuthenticatedMembersMemberIdRoute, + AuthenticatedRolesRoleIdRoute: AuthenticatedRolesRoleIdRoute, + AuthenticatedRolesNewRoute: AuthenticatedRolesNewRoute, AuthenticatedAccountsIndexRoute: AuthenticatedAccountsIndexRoute, AuthenticatedMembersIndexRoute: AuthenticatedMembersIndexRoute, + AuthenticatedRolesIndexRoute: AuthenticatedRolesIndexRoute, } const AuthenticatedRouteWithChildren = AuthenticatedRoute._addFileChildren( diff --git a/packages/admin/src/routes/_authenticated.tsx b/packages/admin/src/routes/_authenticated.tsx index 831fcea..2670ba2 100644 --- a/packages/admin/src/routes/_authenticated.tsx +++ b/packages/admin/src/routes/_authenticated.tsx @@ -14,7 +14,7 @@ import { DropdownMenuSubTrigger, DropdownMenuSubContent, } from '@/components/ui/dropdown-menu' -import { Users, UserRound, HelpCircle, Sun, Moon, Monitor, LogOut, User, Palette } from 'lucide-react' +import { Users, UserRound, HelpCircle, Shield, UserCog, Sun, Moon, Monitor, LogOut, User, Palette } from 'lucide-react' export const Route = createFileRoute('/_authenticated')({ beforeLoad: () => { @@ -69,6 +69,25 @@ function AuthenticatedLayout() { Members +
+ Admin +
+ + + Users + + + + Roles + >(new Set()) + + useEffect(() => { + if (role) { + setName(role.name) + setDescription(role.description ?? '') + setSelectedPerms(new Set(role.permissions ?? [])) + } + }, [role]) + + const updateMutation = useMutation({ + mutationFn: (data: Record) => rbacMutations.updateRole(roleId, data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: rbacKeys.role(roleId) }) + queryClient.invalidateQueries({ queryKey: rbacKeys.roles }) + toast.success('Role updated') + }, + onError: (err) => toast.error(err.message), + }) + + function togglePermission(slug: string) { + const next = new Set(selectedPerms) + if (next.has(slug)) { + next.delete(slug) + } else { + next.add(slug) + } + setSelectedPerms(next) + } + + function toggleDomain(domain: string, allSlugs: string[]) { + const allSelected = allSlugs.every((s) => selectedPerms.has(s)) + const next = new Set(selectedPerms) + for (const slug of allSlugs) { + if (allSelected) next.delete(slug) + else next.add(slug) + } + setSelectedPerms(next) + } + + function handleSave() { + updateMutation.mutate({ + name, + description: description || undefined, + permissionSlugs: Array.from(selectedPerms), + }) + } + + if (roleLoading) { + return ( +
+ + +
+ ) + } + + if (!role) { + return

Role not found

+ } + + // Group permissions by domain + const allPerms = permsData?.data ?? [] + const domains = new Map() + for (const p of allPerms) { + const list = domains.get(p.domain) ?? [] + list.push(p) + domains.set(p.domain, list) + } + + return ( +
+
+ +
+

{role.name}

+
+ {role.slug} + {role.isSystem && System} +
+
+
+ + + + Details + + +
+ + setName(e.target.value)} /> +
+
+ +