Skip to content

Commit

Permalink
feat: add user admin UI
Browse files Browse the repository at this point in the history
  • Loading branch information
sgomez committed Jan 2, 2024
1 parent 2fcc5ea commit 209fa36
Show file tree
Hide file tree
Showing 10 changed files with 282 additions and 31 deletions.
39 changes: 39 additions & 0 deletions src/app/admin/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use client'

import { Card, CardBody, CardHeader, Tab, Tabs } from '@nextui-org/react'
import NextLink from 'next/link'
import { usePathname } from 'next/navigation'
import { ReactNode } from 'react'

export default function Layout({ children }: { children: ReactNode }) {
const pathname = usePathname()

return (
<>
<Card className="flex flex-col gap-2 w-full p-1">
<CardHeader>
<div className="h1 font-bold text-2xl">Gestión de usuarios</div>
</CardHeader>
<CardBody className="overflow-hidden">
<Tabs
size="lg"
aria-label="Options"
selectedKey={pathname}
classNames={{
tab: 'max-w-fit',
tabList: 'w-full',
}}
>
<Tab
key="/admin/users"
href="/admin/users"
title="Usuarios"
as={NextLink}
/>
</Tabs>
<div className="mt-4">{children}</div>
</CardBody>
</Card>
</>
)
}
13 changes: 13 additions & 0 deletions src/app/admin/users/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use server'

import UsersTable from '@/components/users-table/users-table'
import { findUsers } from '@/core/user/infrastructure/actions/find-users'

export default async function Page() {
const users = await findUsers()
return (
<>
<UsersTable users={users} />
</>
)
}
7 changes: 5 additions & 2 deletions src/app/books/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { notFound } from 'next/navigation'

import BookPage from '@/components/book-page'
import BookPage from '@/components/book-page/book-page'
import { findBook } from '@/core/book/infrastructure/actions/find-book'
import getHistoricalLoans from '@/core/loan/infrastructure/actions/get-historical-loans'
import UserResponse from '@/core/user/dto/responses/user.response'
import me from '@/core/user/infrastructure/actions/me'

interface PageParameters {
id: string
Expand All @@ -11,14 +13,15 @@ interface PageParameters {
export default async function Page({ params }: { params: PageParameters }) {
const book = await findBook(params.id)
const historicalLoans = await getHistoricalLoans(params.id)
const user = (await me()) as UserResponse

if (!book) {
return notFound()
}

return (
<>
<BookPage book={book} historicalLoans={historicalLoans} />
<BookPage user={user} book={book} historicalLoans={historicalLoans} />
</>
)
}
6 changes: 3 additions & 3 deletions src/components/book-card/book-card-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ function useController(properties: BookCardFormProperties) {
const { book, me } = properties

const isLoaned = !!book.loan
const isLogged = !!me
const isOwned = isLoaned && isLogged && book.loan.user.id === me.id
const isActive = isLogged && (!isLoaned || isOwned)
const isMember = !!me && me.roles.includes('ROLE_MEMBER')
const isOwned = isLoaned && isMember && book.loan.user.id === me.id
const isActive = isMember && (!isLoaned || isOwned)

const currentAction = useFormState(
isOwned ? returnBook : loanBook,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,28 @@ import {
} from '@nextui-org/react'
import { format } from 'date-fns'
import { es } from 'date-fns/locale'
import Image from 'next/image'
import Link from 'next/link'

import BookBreadcrumbs from '@/components/book-breadcrubs/book-breadcrubs'
import BookCard from '@/components/book-card/book-card'
import BookResponse from '@/core/book/dto/responses/book.response'
import HistoricalLoansResponse from '@/core/loan/dto/responses/historical-loans.response'
import UserResponse from '@/core/user/dto/responses/user.response'

interface BookPageProperties {
book: BookResponse
historicalLoans: HistoricalLoansResponse[]
user: UserResponse
}

export default function BookPage(properties: BookPageProperties) {
const { book, historicalLoans } = properties
const { book, historicalLoans, user } = properties
return (
<>
<main className="flex flex-col gap-4 px-4 md:px-0">
<BookBreadcrumbs title={book.title} />
<div className="flex flex-col md:flex-row gap-4">
<Image
className="h-[297px] object-scale-down w-auto"
alt={book.title}
width={297}
height={387}
src={book.image}
/>
<BookCard book={book} me={user} />
<div className="flex flex-col gap-4">
<h1 className="font-bold text-4xl text-default-800">
{book.title}
Expand All @@ -45,14 +41,16 @@ export default function BookPage(properties: BookPageProperties) {
{book.authors.join(', ')}
</div>
<div className="flex-grow">
<Button
as={Link}
href={`/books/${book.id}/edit`}
prefetch
variant="ghost"
>
Editar
</Button>
{user.roles.includes('ROLE_ADMIN') ? (
<Button
as={Link}
href={`/books/${book.id}/edit`}
prefetch
variant="ghost"
>
Editar
</Button>
) : null}
</div>
<LoanBy book={book} />
</div>
Expand Down
34 changes: 26 additions & 8 deletions src/components/header/header-authenticated-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
ArrowLeftOnRectangleIcon,
BookOpenIcon,
AdjustmentsHorizontalIcon,
ArrowLeftEndOnRectangleIcon,
PlusIcon,
UserIcon,
} from '@heroicons/react/24/outline'
Expand All @@ -21,14 +21,25 @@ interface HeaderAuthenticatedMenuProperties {
user: UserResponse
}

const OPTIONS = [
{ key: 'profile', roles: ['ROLE_USER'], url: '/settings/profile' },
{ key: 'add_book', roles: ['ROLE_ADMIN'], url: '/books/new' },
{ key: 'admin', roles: ['ROLE_ADMIN'], url: '/admin/users' },
{ key: 'signout', roles: ['ROLE_USER'], url: '/signout' },
]

export default function HeaderAuthenticatedMenu(
properties: HeaderAuthenticatedMenuProperties,
) {
const router = useRouter()
const {
user: { email, image, name },
user: { email, image, name, roles },
} = properties

const disabledKeys = OPTIONS.filter(
(option) => !option.roles.some((role) => roles.includes(role)),
).map((option) => option.key)

return (
<>
<Dropdown placement="bottom-end">
Expand All @@ -42,7 +53,11 @@ export default function HeaderAuthenticatedMenu(
fallback={<UserIcon width={24} height={24} />}
/>
</DropdownTrigger>
<DropdownMenu aria-label="Profile actions" variant="flat">
<DropdownMenu
aria-label="Profile actions"
variant="flat"
disabledKeys={disabledKeys}
>
<DropdownItem
key="profile"
onClick={() => router.push('/settings/profile')}
Expand All @@ -63,15 +78,18 @@ export default function HeaderAuthenticatedMenu(
Añadir libro
</DropdownItem>
<DropdownItem
key="books"
startContent={<BookOpenIcon width={24} height={24} />}
key="admin"
startContent={<AdjustmentsHorizontalIcon width={24} height={24} />}
onClick={() => router.push('/admin/users')}
>
Mis libros
Administrar
</DropdownItem>
<DropdownItem
key="signout"
onClick={() => router.push('/signout')}
startContent={<ArrowLeftOnRectangleIcon width={24} height={24} />}
startContent={
<ArrowLeftEndOnRectangleIcon width={24} height={24} />
}
>
Cerrar sesión
</DropdownItem>
Expand Down
30 changes: 30 additions & 0 deletions src/components/users-table/users-table-cell-action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use client'

import { Switch } from '@nextui-org/react'

import UserResponse from '@/core/user/dto/responses/user.response'
import enableUser from '@/core/user/infrastructure/actions/enable-user'

interface UsersTableCellActionProperties {
user: UserResponse
}

export function UsersTableCellAction(
properties: UsersTableCellActionProperties,
) {
const { user } = properties
const enabled = user.roles.includes('ROLE_MEMBER')
if (user.roles.includes('ROLE_ADMIN')) {
return null
}

const selectHandler = async (isSelected: boolean) => {
await enableUser(user.email, isSelected)
}

return (
<Switch defaultSelected={enabled} onValueChange={selectHandler}>
Activar
</Switch>
)
}
46 changes: 46 additions & 0 deletions src/components/users-table/users-table-cell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use client'

import { User } from '@nextui-org/react'
import { Key } from 'react'

import { UsersTableCellAction } from '@/components/users-table/users-table-cell-action'
import UserResponse from '@/core/user/dto/responses/user.response'
import gravatar from '@/lib/utils/gravatar'

interface UsersTableCellProperties {
columnKey: Key
user: UserResponse
}

export default function UsersTableCell(properties: UsersTableCellProperties) {
const { columnKey, user } = properties

switch (columnKey) {
case 'email': {
return (
<User
avatarProps={{ radius: 'lg', src: gravatar(user.email) }}
description={user.email}
name={user.name}
>
{user.email}
</User>
)
}
case 'roles': {
return (
<div className="flex flex-col">
<p className="text-bold text-sm capitalize pl-1">
{user.roles.join(', ')}
</p>
</div>
)
}
case 'actions': {
return <UsersTableCellAction user={user} />
}
default: {
return user[columnKey as keyof UserResponse]
}
}
}
Loading

0 comments on commit 209fa36

Please sign in to comment.