diff --git a/apps/api/src/application/services/accounts/index.ts b/apps/api/src/application/services/accounts/index.ts index 063b224..436076a 100644 --- a/apps/api/src/application/services/accounts/index.ts +++ b/apps/api/src/application/services/accounts/index.ts @@ -385,6 +385,10 @@ export class AccountsService { pagination, }); } + @Catch() + async listAccounts({ pagination }: { pagination: PaginationInput }) { + return this.accountRepository.listAccounts({ pagination }); + } @Catch() async upsertRegistration( diff --git a/apps/api/src/application/services/session/index.ts b/apps/api/src/application/services/session/index.ts index b959c2c..4d4e4bc 100644 --- a/apps/api/src/application/services/session/index.ts +++ b/apps/api/src/application/services/session/index.ts @@ -288,6 +288,7 @@ export class SessionService { authenticated: true, session: { email: account.email, + type: account.type, token: session.token, }, }; diff --git a/apps/api/src/application/usecases/account/index.ts b/apps/api/src/application/usecases/account/index.ts index 6861f54..e720944 100644 --- a/apps/api/src/application/usecases/account/index.ts +++ b/apps/api/src/application/usecases/account/index.ts @@ -14,6 +14,7 @@ export * from "./editCharacter"; export * from "./findCharacter"; export * from "./generateEmailChange"; export * from "./generatePasswordReset"; +export * from "./listAccounts"; export * from "./login"; export * from "./logout"; export * from "./permissioned"; diff --git a/apps/api/src/application/usecases/account/listAccounts/contract.ts b/apps/api/src/application/usecases/account/listAccounts/contract.ts new file mode 100644 index 0000000..d501bd9 --- /dev/null +++ b/apps/api/src/application/usecases/account/listAccounts/contract.ts @@ -0,0 +1,16 @@ +import type z from "zod"; +import { ListAccountSchema } from "@/shared/schemas/ListAccounts"; +import { createPaginateSchema, InputPageSchema } from "@/shared/utils/paginate"; + +export const ListAccountsContractSchema = { + input: InputPageSchema, + output: createPaginateSchema(ListAccountSchema), +}; + +export type ListAccountsContractInput = z.infer< + typeof ListAccountsContractSchema.input +>; + +export type ListAccountsContractOutput = z.infer< + typeof ListAccountsContractSchema.output +>; diff --git a/apps/api/src/application/usecases/account/listAccounts/index.ts b/apps/api/src/application/usecases/account/listAccounts/index.ts new file mode 100644 index 0000000..2924250 --- /dev/null +++ b/apps/api/src/application/usecases/account/listAccounts/index.ts @@ -0,0 +1,34 @@ +import { inject, injectable } from "tsyringe"; +import type { AccountsService } from "@/application/services"; +import type { Pagination } from "@/domain/modules"; +import { TOKENS } from "@/infra/di/tokens"; +import type { UseCase } from "@/shared/interfaces/usecase"; +import type { + ListAccountsContractInput, + ListAccountsContractOutput, +} from "./contract"; + +@injectable() +export class ListAccountsUseCase + implements UseCase +{ + constructor( + @inject(TOKENS.AccountsService) + private readonly accountsService: AccountsService, + @inject(TOKENS.Pagination) private readonly pagination: Pagination, + ) {} + + async execute( + input: ListAccountsContractInput, + ): Promise { + const { storeHistory, total } = await this.accountsService.listAccounts({ + pagination: input, + }); + + return this.pagination.paginate(storeHistory, { + page: input.page ?? 1, + size: input.size ?? 10, + total, + }); + } +} diff --git a/apps/api/src/application/usecases/factory.ts b/apps/api/src/application/usecases/factory.ts index f89abfb..497924b 100644 --- a/apps/api/src/application/usecases/factory.ts +++ b/apps/api/src/application/usecases/factory.ts @@ -95,6 +95,7 @@ export class UseCasesFactory { const changePasswordWithToken = this.di.resolve( TOKENS.AccountChangePasswordWithTokenUseCase, ); + const listAccounts = this.di.resolve(TOKENS.ListAccountsUseCase); const changeEmailWithPassword = this.di.resolve( TOKENS.AccountChangeEmailWithPasswordUseCase, ); @@ -134,6 +135,7 @@ export class UseCasesFactory { changePasswordWithOld, generatePasswordReset, changePasswordWithToken, + listAccounts, changeEmailWithPassword, generateEmailChange, previewEmailChange, diff --git a/apps/api/src/application/usecases/session/info/contract.ts b/apps/api/src/application/usecases/session/info/contract.ts index b74f76e..2121f20 100644 --- a/apps/api/src/application/usecases/session/info/contract.ts +++ b/apps/api/src/application/usecases/session/info/contract.ts @@ -8,6 +8,7 @@ export const SessionInfoContractSchema = { .object({ token: z.string(), email: z.email(), + type: z.number().nullable(), }) .nullable(), }), diff --git a/apps/api/src/domain/repositories/account/index.ts b/apps/api/src/domain/repositories/account/index.ts index 372a50e..462afa0 100644 --- a/apps/api/src/domain/repositories/account/index.ts +++ b/apps/api/src/domain/repositories/account/index.ts @@ -210,6 +210,27 @@ export class AccountRepository { }; } + async listAccounts(opts?: { pagination: PaginationInput }) { + const page = opts?.pagination.page ?? 1; + const size = opts?.pagination.size ?? 10; + + const [storeHistory, total] = await Promise.all([ + this.prisma.accounts.findMany({ + orderBy: { + name: "desc", + }, + skip: (page - 1) * size, + take: size, + }), + this.prisma.accounts.count(), + ]); + + return { + storeHistory, + total, + }; + } + async details(email: string) { return this.prisma.accounts.findFirst({ where: { diff --git a/apps/api/src/infra/di/containers/usecases.ts b/apps/api/src/infra/di/containers/usecases.ts index 4fdec69..3148bb6 100644 --- a/apps/api/src/infra/di/containers/usecases.ts +++ b/apps/api/src/infra/di/containers/usecases.ts @@ -27,6 +27,7 @@ import { ChangePasswordWithTokenUseCase, ConfigInfoUseCase, ConfigUpdateUseCase, + ListAccountsUseCase, LostAccountFindByEmailOrCharacterNameUseCase, LostAccountGeneratePasswordResetUseCase, LostAccountResetPasswordWithRecoveryKeyUseCase, @@ -128,6 +129,9 @@ export function registerUseCases() { { useClass: ChangePasswordWithTokenUseCase }, { lifecycle: Lifecycle.ResolutionScoped }, ); + container.register(TOKENS.ListAccountsUseCase, { + useClass: ListAccountsUseCase, + }); container.register( TOKENS.AccountTwoFactorSetupUseCase, { useClass: AccountTwoFactorSetupUseCase }, diff --git a/apps/api/src/infra/di/tokens.ts b/apps/api/src/infra/di/tokens.ts index d994a9a..6812720 100644 --- a/apps/api/src/infra/di/tokens.ts +++ b/apps/api/src/infra/di/tokens.ts @@ -40,6 +40,7 @@ import type { ChangePasswordWithTokenUseCase, ConfigInfoUseCase, ConfigUpdateUseCase, + ListAccountsUseCase, LostAccountFindByEmailOrCharacterNameUseCase, LostAccountGeneratePasswordResetUseCase, LostAccountResetPasswordWithRecoveryKeyUseCase, @@ -53,6 +54,7 @@ import type { TibiaLoginUseCase, WorldsListUseCase, } from "@/application/usecases"; + import type { Mailer, OtsServerClient, Prisma, Redis } from "@/domain/clients"; import type { AppLivePublisher } from "@/domain/clients/live/types"; import type { ExecutionContext } from "@/domain/context"; @@ -214,6 +216,7 @@ export const TOKENS = { AccountChangePasswordWithTokenUseCase: token( "AccountChangePasswordWithTokenUseCase", ), + ListAccountsUseCase: token("ListAccountsUseCase"), AccountChangeEmailWithPasswordUseCase: token( "AccountChangeEmailWithPasswordUseCase", diff --git a/apps/api/src/presentation/v1/routes/admin/accounts/index.ts b/apps/api/src/presentation/v1/routes/admin/accounts/index.ts new file mode 100644 index 0000000..282e37e --- /dev/null +++ b/apps/api/src/presentation/v1/routes/admin/accounts/index.ts @@ -0,0 +1,6 @@ +import { base } from "@/infra/rpc/base"; +import { listAccountsRouter } from "./list"; + +export const adminAccountsRouter = base.prefix("/accounts").router({ + list: listAccountsRouter, +}); diff --git a/apps/api/src/presentation/v1/routes/admin/accounts/list/index.ts b/apps/api/src/presentation/v1/routes/admin/accounts/list/index.ts new file mode 100644 index 0000000..da85ad2 --- /dev/null +++ b/apps/api/src/presentation/v1/routes/admin/accounts/list/index.ts @@ -0,0 +1,22 @@ +import { ListAccountsContractSchema } from "@/application/usecases/account/listAccounts/contract"; +import { isPermissionedProcedure } from "@/presentation/procedures/isPermissioned"; + +export const listAccountsRouter = isPermissionedProcedure + .meta({ + permission: { + type: "GAME_MASTER", + }, + }) + .route({ + method: "GET", + path: "/list", + summary: "List Accounts", + successStatus: 200, + description: + "Retrieves a list of accounts registered on the server. Only GAME_MASTER and ADMIN users are allowed to perform this action", + }) + .input(ListAccountsContractSchema.input) + .output(ListAccountsContractSchema.output) + .handler(async ({ context, input }) => { + return await context.usecases.account.listAccounts.execute(input); + }); diff --git a/apps/api/src/presentation/v1/routes/admin/index.ts b/apps/api/src/presentation/v1/routes/admin/index.ts new file mode 100644 index 0000000..443e8d5 --- /dev/null +++ b/apps/api/src/presentation/v1/routes/admin/index.ts @@ -0,0 +1,6 @@ +import { base } from "@/infra/rpc/base"; +import { adminAccountsRouter } from "./accounts"; + +export const adminRouter = base.prefix("/admin").tag("Admin").router({ + accounts: adminAccountsRouter, +}); diff --git a/apps/api/src/presentation/v1/routes/index.ts b/apps/api/src/presentation/v1/routes/index.ts index 0e55c8d..71ed801 100644 --- a/apps/api/src/presentation/v1/routes/index.ts +++ b/apps/api/src/presentation/v1/routes/index.ts @@ -1,5 +1,6 @@ import { base } from "@/infra/rpc/base"; import { accountsRouter } from "./accounts"; +import { adminRouter } from "./admin"; import { clientRouter } from "./client"; import { configRouter } from "./config"; import { lostAccountRouter } from "./lost"; @@ -8,6 +9,7 @@ import { sessionRouter } from "./session"; import { worldsRouter } from "./worlds"; export const router = base.router({ + admin: adminRouter, ping: pingRoute, client: clientRouter, accounts: accountsRouter, diff --git a/apps/api/src/shared/schemas/ListAccounts.ts b/apps/api/src/shared/schemas/ListAccounts.ts new file mode 100644 index 0000000..9a3a483 --- /dev/null +++ b/apps/api/src/shared/schemas/ListAccounts.ts @@ -0,0 +1,8 @@ +import z from "zod"; + +export const ListAccountSchema = z.object({ + id: z.number(), + name: z.string().nullable(), + email: z.email(), + type: z.number(), +}); diff --git a/apps/web/.env.example b/apps/web/.env.example new file mode 100644 index 0000000..7f33d16 --- /dev/null +++ b/apps/web/.env.example @@ -0,0 +1,5 @@ +VITE_SHOW_DEVTOOLS=true + +# ==== RPC ==== # +VITE_MIFORGE_RPC_URL="http://localhost:4000" +VITE_MIFORGE_RPC_PATH="/v1/rpc" \ No newline at end of file diff --git a/apps/web/src/components/Menu/Item/index.tsx b/apps/web/src/components/Menu/Item/index.tsx index 3ee4908..d4bb6be 100644 --- a/apps/web/src/components/Menu/Item/index.tsx +++ b/apps/web/src/components/Menu/Item/index.tsx @@ -1,12 +1,7 @@ import { Link, type LinkProps, useRouterState } from "@tanstack/react-router"; import { useEffect, useState } from "react"; import { cn } from "@/sdk/utils/cn"; - -const Icons = { - news: "/assets/icons/32/news-menu.gif", - sphere: "/assets/icons/32/armillary_sphere.gif", - munster: "/assets/icons/32/baby_munster.gif", -}; +import { Icons } from ".."; type Props = { label: string; @@ -38,7 +33,8 @@ export const MenuItem = ({ icon, label, menus = [] }: Props) => { return (
-
{ {label} -
+
{ }} >
{menus.map((subMenu) => { const isActive = routerState.location.pathname === subMenu.to; diff --git a/apps/web/src/components/Menu/index.tsx b/apps/web/src/components/Menu/index.tsx index ea93c61..8729870 100644 --- a/apps/web/src/components/Menu/index.tsx +++ b/apps/web/src/components/Menu/index.tsx @@ -1,20 +1,33 @@ +import type { LinkProps } from "@tanstack/react-router"; import { MenuBox } from "@/components/Box/Menu"; import { MenuItem } from "./Item"; -export const Menu = () => { +export const Icons = { + management: "/assets/icons/32/loremaster_doll.gif", + news: "/assets/icons/32/news-menu.gif", + sphere: "/assets/icons/32/armillary_sphere.gif", + munster: "/assets/icons/32/baby_munster.gif", +}; + +interface MenuProps { + items: Array<{ + label: string; + icon: keyof typeof Icons; + menus: Array<{ + label: string; + to: LinkProps["to"]; + hot?: boolean; + }>; + }>; +} + +export const Menu = ({ items }: MenuProps) => { return (
- - + {items.map((item) => ( + + ))}
); diff --git a/apps/web/src/layout/Navigation/index.tsx b/apps/web/src/layout/Navigation/index.tsx index 8efe2a8..261f415 100644 --- a/apps/web/src/layout/Navigation/index.tsx +++ b/apps/web/src/layout/Navigation/index.tsx @@ -1,8 +1,11 @@ import { BoxDownload } from "@/components/Box/Download"; import { BoxLogin } from "@/components/Box/Login"; +import { useSession } from "@/sdk/contexts/session"; import { Menu } from "../../components/Menu"; export const Navigation = () => { + const role: number | null = useSession().session?.type || null; + return ( ); }; diff --git a/apps/web/src/routeTree.gen.ts b/apps/web/src/routeTree.gen.ts index 847fd5c..39b3a14 100644 --- a/apps/web/src/routeTree.gen.ts +++ b/apps/web/src/routeTree.gen.ts @@ -30,6 +30,9 @@ import { Route as AuthAccountPlayerNameDeleteIndexRouteImport } from './routes/_ const PublicTermsIndexLazyRouteImport = createFileRoute('/_public/terms/')() const Not_authLoginIndexLazyRouteImport = createFileRoute('/_not_auth/login/')() +const AuthListAccountsIndexLazyRouteImport = createFileRoute( + '/_auth/list-accounts/', +)() const Not_authAccountLostIndexLazyRouteImport = createFileRoute( '/_not_auth/account/lost/', )() @@ -57,6 +60,9 @@ const Not_authAccountLostEmailIndexLazyRouteImport = createFileRoute( const Not_authAccountEmailConfirmIndexLazyRouteImport = createFileRoute( '/_not_auth/account/$email/confirm/', )() +const AuthAdminAccountsListIndexLazyRouteImport = createFileRoute( + '/_auth/admin/accounts/list/', +)() const AuthAccountEmailChangeIndexLazyRouteImport = createFileRoute( '/_auth/account/email/change/', )() @@ -101,6 +107,14 @@ const Not_authLoginIndexLazyRoute = Not_authLoginIndexLazyRouteImport.update({ } as any).lazy(() => import('./routes/_not_auth/login/index.lazy').then((d) => d.Route), ) +const AuthListAccountsIndexLazyRoute = + AuthListAccountsIndexLazyRouteImport.update({ + id: '/list-accounts/', + path: '/list-accounts/', + getParentRoute: () => AuthRouteRoute, + } as any).lazy(() => + import('./routes/_auth/list-accounts/index.lazy').then((d) => d.Route), + ) const PublicWorldsIndexRoute = PublicWorldsIndexRouteImport.update({ id: '/worlds/', path: '/worlds/', @@ -216,6 +230,16 @@ const Not_authAccountEmailConfirmIndexLazyRoute = (d) => d.Route, ), ) +const AuthAdminAccountsListIndexLazyRoute = + AuthAdminAccountsListIndexLazyRouteImport.update({ + id: '/admin/accounts/list/', + path: '/admin/accounts/list/', + getParentRoute: () => AuthRouteRoute, + } as any).lazy(() => + import('./routes/_auth/admin/accounts/list/index.lazy').then( + (d) => d.Route, + ), + ) const AuthAccountEmailChangeIndexLazyRoute = AuthAccountEmailChangeIndexLazyRouteImport.update({ id: '/change/', @@ -333,6 +357,7 @@ export interface FileRoutesByFullPath { '/account/email': typeof AuthAccountEmailRouteRouteWithChildren '/account': typeof AuthAccountIndexRoute '/worlds': typeof PublicWorldsIndexRoute + '/list-accounts': typeof AuthListAccountsIndexLazyRoute '/login': typeof Not_authLoginIndexLazyRoute '/terms': typeof PublicTermsIndexLazyRoute '/account/lost/$email': typeof Not_authAccountLostEmailRouteRouteWithChildren @@ -349,6 +374,7 @@ export interface FileRoutesByFullPath { '/account/2fa/unlink': typeof AuthAccount2faUnlinkIndexRoute '/account/player/create': typeof AuthAccountPlayerCreateIndexRoute '/account/email/change': typeof AuthAccountEmailChangeIndexLazyRoute + '/admin/accounts/list': typeof AuthAdminAccountsListIndexLazyRoute '/account/$email/confirm': typeof Not_authAccountEmailConfirmIndexLazyRoute '/account/lost/$email/': typeof Not_authAccountLostEmailIndexLazyRoute '/account/player/$name/delete': typeof AuthAccountPlayerNameDeleteIndexRoute @@ -364,6 +390,7 @@ export interface FileRoutesByTo { '/account/email': typeof AuthAccountEmailRouteRouteWithChildren '/account': typeof AuthAccountIndexRoute '/worlds': typeof PublicWorldsIndexRoute + '/list-accounts': typeof AuthListAccountsIndexLazyRoute '/login': typeof Not_authLoginIndexLazyRoute '/terms': typeof PublicTermsIndexLazyRoute '/account/lost/$token': typeof Not_authAccountLostTokenRouteRouteWithChildren @@ -379,6 +406,7 @@ export interface FileRoutesByTo { '/account/2fa/unlink': typeof AuthAccount2faUnlinkIndexRoute '/account/player/create': typeof AuthAccountPlayerCreateIndexRoute '/account/email/change': typeof AuthAccountEmailChangeIndexLazyRoute + '/admin/accounts/list': typeof AuthAdminAccountsListIndexLazyRoute '/account/$email/confirm': typeof Not_authAccountEmailConfirmIndexLazyRoute '/account/lost/$email': typeof Not_authAccountLostEmailIndexLazyRoute '/account/player/$name/delete': typeof AuthAccountPlayerNameDeleteIndexRoute @@ -398,6 +426,7 @@ export interface FileRoutesById { '/_auth/account/email': typeof AuthAccountEmailRouteRouteWithChildren '/_auth/account/': typeof AuthAccountIndexRoute '/_public/worlds/': typeof PublicWorldsIndexRoute + '/_auth/list-accounts/': typeof AuthListAccountsIndexLazyRoute '/_not_auth/login/': typeof Not_authLoginIndexLazyRoute '/_public/terms/': typeof PublicTermsIndexLazyRoute '/_not_auth/account/lost/$email': typeof Not_authAccountLostEmailRouteRouteWithChildren @@ -414,6 +443,7 @@ export interface FileRoutesById { '/_auth/account/2fa/unlink/': typeof AuthAccount2faUnlinkIndexRoute '/_auth/account/player/create/': typeof AuthAccountPlayerCreateIndexRoute '/_auth/account/email/change/': typeof AuthAccountEmailChangeIndexLazyRoute + '/_auth/admin/accounts/list/': typeof AuthAdminAccountsListIndexLazyRoute '/_not_auth/account/$email/confirm/': typeof Not_authAccountEmailConfirmIndexLazyRoute '/_not_auth/account/lost/$email/': typeof Not_authAccountLostEmailIndexLazyRoute '/_auth/account/player/$name/delete/': typeof AuthAccountPlayerNameDeleteIndexRoute @@ -431,6 +461,7 @@ export interface FileRouteTypes { | '/account/email' | '/account' | '/worlds' + | '/list-accounts' | '/login' | '/terms' | '/account/lost/$email' @@ -447,6 +478,7 @@ export interface FileRouteTypes { | '/account/2fa/unlink' | '/account/player/create' | '/account/email/change' + | '/admin/accounts/list' | '/account/$email/confirm' | '/account/lost/$email/' | '/account/player/$name/delete' @@ -462,6 +494,7 @@ export interface FileRouteTypes { | '/account/email' | '/account' | '/worlds' + | '/list-accounts' | '/login' | '/terms' | '/account/lost/$token' @@ -477,6 +510,7 @@ export interface FileRouteTypes { | '/account/2fa/unlink' | '/account/player/create' | '/account/email/change' + | '/admin/accounts/list' | '/account/$email/confirm' | '/account/lost/$email' | '/account/player/$name/delete' @@ -495,6 +529,7 @@ export interface FileRouteTypes { | '/_auth/account/email' | '/_auth/account/' | '/_public/worlds/' + | '/_auth/list-accounts/' | '/_not_auth/login/' | '/_public/terms/' | '/_not_auth/account/lost/$email' @@ -511,6 +546,7 @@ export interface FileRouteTypes { | '/_auth/account/2fa/unlink/' | '/_auth/account/player/create/' | '/_auth/account/email/change/' + | '/_auth/admin/accounts/list/' | '/_not_auth/account/$email/confirm/' | '/_not_auth/account/lost/$email/' | '/_auth/account/player/$name/delete/' @@ -572,6 +608,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof Not_authLoginIndexLazyRouteImport parentRoute: typeof Not_authRouteRoute } + '/_auth/list-accounts/': { + id: '/_auth/list-accounts/' + path: '/list-accounts' + fullPath: '/list-accounts' + preLoaderRoute: typeof AuthListAccountsIndexLazyRouteImport + parentRoute: typeof AuthRouteRoute + } '/_public/worlds/': { id: '/_public/worlds/' path: '/worlds' @@ -670,6 +713,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof Not_authAccountEmailConfirmIndexLazyRouteImport parentRoute: typeof Not_authRouteRoute } + '/_auth/admin/accounts/list/': { + id: '/_auth/admin/accounts/list/' + path: '/admin/accounts/list' + fullPath: '/admin/accounts/list' + preLoaderRoute: typeof AuthAdminAccountsListIndexLazyRouteImport + parentRoute: typeof AuthRouteRoute + } '/_auth/account/email/change/': { id: '/_auth/account/email/change/' path: '/change' @@ -791,6 +841,7 @@ const AuthAccountEmailRouteRouteWithChildren = interface AuthRouteRouteChildren { AuthAccountEmailRouteRoute: typeof AuthAccountEmailRouteRouteWithChildren AuthAccountIndexRoute: typeof AuthAccountIndexRoute + AuthListAccountsIndexLazyRoute: typeof AuthListAccountsIndexLazyRoute AuthAccountAudit_historyIndexLazyRoute: typeof AuthAccountAudit_historyIndexLazyRoute AuthAccountCoins_historyIndexLazyRoute: typeof AuthAccountCoins_historyIndexLazyRoute AuthAccountDetailsIndexLazyRoute: typeof AuthAccountDetailsIndexLazyRoute @@ -799,6 +850,7 @@ interface AuthRouteRouteChildren { AuthAccount2faLinkIndexRoute: typeof AuthAccount2faLinkIndexRoute AuthAccount2faUnlinkIndexRoute: typeof AuthAccount2faUnlinkIndexRoute AuthAccountPlayerCreateIndexRoute: typeof AuthAccountPlayerCreateIndexRoute + AuthAdminAccountsListIndexLazyRoute: typeof AuthAdminAccountsListIndexLazyRoute AuthAccountPlayerNameDeleteIndexRoute: typeof AuthAccountPlayerNameDeleteIndexRoute AuthAccountPlayerNameEditIndexRoute: typeof AuthAccountPlayerNameEditIndexRoute AuthAccountPlayerNameUndeleteIndexRoute: typeof AuthAccountPlayerNameUndeleteIndexRoute @@ -807,6 +859,7 @@ interface AuthRouteRouteChildren { const AuthRouteRouteChildren: AuthRouteRouteChildren = { AuthAccountEmailRouteRoute: AuthAccountEmailRouteRouteWithChildren, AuthAccountIndexRoute: AuthAccountIndexRoute, + AuthListAccountsIndexLazyRoute: AuthListAccountsIndexLazyRoute, AuthAccountAudit_historyIndexLazyRoute: AuthAccountAudit_historyIndexLazyRoute, AuthAccountCoins_historyIndexLazyRoute: @@ -818,6 +871,7 @@ const AuthRouteRouteChildren: AuthRouteRouteChildren = { AuthAccount2faLinkIndexRoute: AuthAccount2faLinkIndexRoute, AuthAccount2faUnlinkIndexRoute: AuthAccount2faUnlinkIndexRoute, AuthAccountPlayerCreateIndexRoute: AuthAccountPlayerCreateIndexRoute, + AuthAdminAccountsListIndexLazyRoute: AuthAdminAccountsListIndexLazyRoute, AuthAccountPlayerNameDeleteIndexRoute: AuthAccountPlayerNameDeleteIndexRoute, AuthAccountPlayerNameEditIndexRoute: AuthAccountPlayerNameEditIndexRoute, AuthAccountPlayerNameUndeleteIndexRoute: diff --git a/apps/web/src/routes/_auth/admin/accounts/list/index.lazy.tsx b/apps/web/src/routes/_auth/admin/accounts/list/index.lazy.tsx new file mode 100644 index 0000000..a436987 --- /dev/null +++ b/apps/web/src/routes/_auth/admin/accounts/list/index.lazy.tsx @@ -0,0 +1,10 @@ +import { createLazyFileRoute } from "@tanstack/react-router"; +import { ListAccounts } from "@/sections/list_acconts"; + +export const Route = createLazyFileRoute("/_auth/admin/accounts/list/")({ + component: RouteComponent, +}); + +function RouteComponent() { + return ; +} diff --git a/apps/web/src/routes/_auth/list-accounts/index.lazy.tsx b/apps/web/src/routes/_auth/list-accounts/index.lazy.tsx new file mode 100644 index 0000000..a7d582c --- /dev/null +++ b/apps/web/src/routes/_auth/list-accounts/index.lazy.tsx @@ -0,0 +1,10 @@ +import { createLazyFileRoute } from "@tanstack/react-router"; +import { ListAccounts } from "@/sections/list_acconts"; + +export const Route = createLazyFileRoute("/_auth/list-accounts/")({ + component: RouteComponent, +}); + +function RouteComponent() { + return ; +} diff --git a/apps/web/src/sections/list_acconts/index.tsx b/apps/web/src/sections/list_acconts/index.tsx new file mode 100644 index 0000000..d46abe1 --- /dev/null +++ b/apps/web/src/sections/list_acconts/index.tsx @@ -0,0 +1,158 @@ +import { keepPreviousData, useQuery } from "@tanstack/react-query"; +import { Link } from "@tanstack/react-router"; +import { PaginationControls } from "@/components/Pagination"; +import { + usePagination, + usePaginationControls, +} from "@/sdk/hooks/usePagination"; +import { api } from "@/sdk/lib/api/factory"; +import { cn } from "@/sdk/utils/cn"; +import { Container } from "@/ui/Container"; +import { InnerContainer } from "@/ui/Container/Inner"; +import { Section } from "@/ui/Section"; +import { SectionHeader } from "@/ui/Section/Header"; +import { InnerSection } from "@/ui/Section/Inner"; +import { Tooltip } from "@/ui/Tooltip"; + +export const ListAccounts = () => { + const { pagination, setPagination } = usePagination({ + initialPage: 1, + initialSize: 10, + }); + const { data, refetch, isFetching } = useQuery( + api.query.miforge.admin.accounts.list.queryOptions({ + placeholderData: keepPreviousData, + input: { + page: pagination?.page, + size: pagination?.size, + }, + }), + ); + + const { + canGoToNextPage, + canGoToPreviousPage, + goToNextPage, + goToPreviousPage, + } = usePaginationControls({ + pagination, + setPagination, + totalItems: data?.meta?.total, + totalPages: data?.meta?.totalPages, + }); + + const accounts = data?.results ?? []; + + const roles = [ + "GUEST", + "PLAYER", + "TUTOR", + "SENIOR_TUTOR", + "GAME_MASTER", + "ADMIN", + ]; + + return ( +
+ +

Management

+
+ + + + + } + > + + + + + + + + + + + {accounts.map((account, index) => { + return ( + + + + + + + + ); + })} + +
+ # + + Name + + Email + + Role + + Action +
+ + {index + 1}. + + +

{account.name}

+
+ {account.email} + +

{roles[account.type]}

+
+ [ + + Detailis + + ] +
+
+ + + +
+
+
+ ); +};