Skip to content

Commit

Permalink
feat: implement user autocomplete fields
Browse files Browse the repository at this point in the history
  • Loading branch information
beeman committed Jan 17, 2024
1 parent 198add8 commit 7f754f9
Show file tree
Hide file tree
Showing 17 changed files with 304 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { Prisma } from '@prisma/client'
import { Prisma, UserStatus } from '@prisma/client'
import { UserFindManyUserInput } from '../dto/user-find-many-user.input'

export function getUserUserWhereInput(input: UserFindManyUserInput): Prisma.UserWhereInput {
const where: Prisma.UserWhereInput = {}
const where: Prisma.UserWhereInput = {
status: {
in: [UserStatus.Active],
},
}

if (input.search) {
where.OR = [
Expand Down
2 changes: 2 additions & 0 deletions libs/web/dev/feature/src/lib/dev-admin-routes.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { UiContainer, UiTabRoutes } from '@pubkey-ui/core'
import { DevIdentityWizard } from './dev-identity-wizard'
import { DevNew } from './dev-new'
import { DevUserAutocomplete } from './dev-user-autocomplete'

export default function DevAdminRoutes() {
return (
Expand All @@ -10,6 +11,7 @@ export default function DevAdminRoutes() {
tabs={[
{ path: 'new', label: 'New', element: <DevNew /> },
{ path: 'identity-wizard', label: 'Identity Wizard', element: <DevIdentityWizard /> },
{ path: 'user-autocomplete', label: 'User Autocomplete', element: <DevUserAutocomplete /> },
]}
/>
</UiContainer>
Expand Down
66 changes: 66 additions & 0 deletions libs/web/dev/feature/src/lib/dev-user-autocomplete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { SimpleGrid, Text } from '@mantine/core'
import type { User } from '@pubkey-stack/sdk'
import { AdminUserUiSearch, UserUiAvatar, UserUiSearch, UserUiTitle } from '@pubkey-stack/web-user-ui'
import { UiCard, UiDebug, UiInfo, UiStack } from '@pubkey-ui/core'
import { useState } from 'react'

export function DevUserAutocomplete() {
const [adminResult, setAdminResult] = useState<User | undefined>(undefined)
const [userResult, setUserResult] = useState<User | undefined>(undefined)

return (
<UiStack>
<UiCard>
<UiStack>
<UiInfo title="AdminUserUiSearch" message="Search for a user as an admin (all users will be shown)" />
<AdminUserUiSearch select={setAdminResult} />
{adminResult ? <ShowUserItems user={adminResult} /> : null}
</UiStack>
</UiCard>
<UiCard>
<UiStack>
<UiInfo title="UserUiSearch" message="Search for a user as a normal user. Only active users are shown" />
<UserUiSearch select={setUserResult} />
{userResult ? <ShowUserItems user={userResult} /> : null}
</UiStack>
</UiCard>
</UiStack>
)
}

function ShowUserItems({ user }: { user: User }) {
return (
<UiStack>
<SimpleGrid cols={3}>
<UiCard
title={
<Text size="sm" c="dimmed">
UserUiTitle with link
</Text>
}
>
<UserUiTitle user={user} to={user.profileUrl} />
</UiCard>
<UiCard
title={
<Text size="sm" c="dimmed">
UserUiTitle
</Text>
}
>
<UserUiTitle user={user} />
</UiCard>
<UiCard
title={
<Text size="sm" c="dimmed">
UserUiAvatar
</Text>
}
>
<UserUiAvatar user={user} />
</UiCard>
</SimpleGrid>
<UiDebug data={user} />
</UiStack>
)
}
1 change: 1 addition & 0 deletions libs/web/ui/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './lib/ui-header-profile'
export * from './lib/ui-modal-button'
export * from './lib/ui-page-limit'
export * from './lib/ui-search-field'
export * from './lib/ui-select-enum-option'
19 changes: 19 additions & 0 deletions libs/web/ui/core/src/lib/ui-select-enum-option.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Select } from '@mantine/core'

export function UiSelectEnumOption<T>({
value,
onChange,
options,
}: {
value: T | undefined
onChange: (value: T | undefined) => void
options: { value: string; label: string }[]
}) {
return (
<Select
value={value?.toString() ?? ''}
onChange={(value) => onChange(value === '' ? undefined : (value as T))}
data={options}
/>
)
}
21 changes: 15 additions & 6 deletions libs/web/user/data-access/src/lib/use-admin-find-many-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { toastError, toastSuccess } from '@pubkey-ui/core'
import { useQuery } from '@tanstack/react-query'
import { useState } from 'react'

export function useAdminFindManyUser() {
export function useAdminFindManyUser(props?: AdminFindManyUserInput) {
const sdk = useSdk()
const [role, setRole] = useState<UserRole | undefined>(undefined)
const [status, setStatus] = useState<UserStatus | undefined>(undefined)
const [limit, setLimit] = useState(10)
const [page, setPage] = useState(1)
const [limit, setLimit] = useState(props?.limit ?? 10)
const [page, setPage] = useState(props?.page ?? 1)
const [search, setSearch] = useState<string>('')

const input: AdminFindManyUserInput = { limit, page, role, search, status }
Expand All @@ -32,9 +32,18 @@ export function useAdminFindManyUser() {
setLimit,
setPage,
},
setRole,
setSearch,
setStatus,
setRole: (role: UserRole | undefined) => {
setPage(1)
setRole(role)
},
setSearch: (search: string) => {
setPage(1)
setSearch(search)
},
setStatus: (status: UserStatus | undefined) => {
setPage(1)
setStatus(status)
},
createUser: (input: AdminCreateUserInput) =>
sdk
.adminCreateUser({ input })
Expand Down
21 changes: 18 additions & 3 deletions libs/web/user/data-access/src/lib/use-user-find-many-user.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
import { UserFindManyUserInput } from '@pubkey-stack/sdk'
import { useSdk } from '@pubkey-stack/web-core-data-access'
import { useQuery } from '@tanstack/react-query'
import { useState } from 'react'

export function useUserFindManyUser(input: UserFindManyUserInput) {
export function useUserFindManyUser(props?: UserFindManyUserInput) {
const sdk = useSdk()
const [limit, setLimit] = useState(props?.limit ?? 10)
const [page, setPage] = useState(props?.page ?? 1)
const [search, setSearch] = useState<string>('')

const input: UserFindManyUserInput = { limit, page, search }
const query = useQuery({
queryKey: ['user', 'find-many-user', input],
queryFn: () => sdk.userFindManyUser({ input }).then((res) => res.data),
retry: 0,
})
const total = query.data?.paging.meta?.totalCount ?? 0
const items = query.data?.paging.data ?? []

return {
data: query.data?.paging.data ?? [],
meta: query.data?.paging.meta,
items,
query,
pagination: {
limit,
page,
total,
setLimit,
setPage,
},
setSearch,
}
}
23 changes: 5 additions & 18 deletions libs/web/user/feature/src/lib/admin-user-list-feature.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Button, Group, Select } from '@mantine/core'
import { getEnumOptions, UserRole, UserStatus } from '@pubkey-stack/sdk'
import { Button, Group } from '@mantine/core'
import { UiPageLimit, UiSearchField } from '@pubkey-stack/web-ui-core'
import { useAdminFindManyUser } from '@pubkey-stack/web-user-data-access'
import { AdminUserUiTable } from '@pubkey-stack/web-user-ui'
import { UiBack, UiDebugModal, UiInfo, UiLoader, UiPage } from '@pubkey-ui/core'
import { Link } from 'react-router-dom'
import { AdminUserUiSelectRole } from './admin-user-ui-select-role'
import { AdminUserUiSelectStatus } from './admin-user-ui-select-status'

export function AdminUserListFeature() {
const { deleteUser, items, pagination, query, role, setRole, setSearch, setStatus, status } = useAdminFindManyUser()
Expand All @@ -24,22 +25,8 @@ export function AdminUserListFeature() {
>
<Group>
<UiSearchField placeholder="Search user" setSearch={setSearch} />
<Select
value={role?.toString() ?? ''}
onChange={(role) => {
pagination.setPage(1)
setRole(role === '' ? undefined : (role as UserRole))
}}
data={[{ value: '', label: 'Filter by role' }, ...getEnumOptions(UserRole)]}
/>
<Select
value={status?.toString() ?? ''}
onChange={(status) => {
pagination.setPage(1)
setStatus(status === '' ? undefined : (status as UserStatus))
}}
data={[{ value: '', label: 'Filter by status' }, ...getEnumOptions(UserStatus)]}
/>
<AdminUserUiSelectRole value={role} onChange={setRole} />
<AdminUserUiSelectStatus value={status} onChange={setStatus} />
<UiPageLimit limit={pagination.limit} setLimit={pagination.setLimit} setPage={pagination.setPage} />
</Group>

Expand Down
18 changes: 18 additions & 0 deletions libs/web/user/feature/src/lib/admin-user-ui-select-role.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { getEnumOptions, UserRole } from '@pubkey-stack/sdk'
import { UiSelectEnumOption } from '@pubkey-stack/web-ui-core'

export function AdminUserUiSelectRole({
value,
onChange,
}: {
value: UserRole | undefined
onChange: (role: UserRole | undefined) => void
}) {
return (
<UiSelectEnumOption<UserRole>
value={value}
onChange={onChange}
options={[{ value: '', label: 'Filter by role' }, ...getEnumOptions(UserRole)]}
/>
)
}
18 changes: 18 additions & 0 deletions libs/web/user/feature/src/lib/admin-user-ui-select-status.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { getEnumOptions, UserStatus } from '@pubkey-stack/sdk'
import { UiSelectEnumOption } from '@pubkey-stack/web-ui-core'

export function AdminUserUiSelectStatus({
value,
onChange,
}: {
value: UserStatus | undefined
onChange: (value: UserStatus | undefined) => void
}) {
return (
<UiSelectEnumOption<UserStatus>
value={value}
onChange={onChange}
options={[{ value: '', label: 'Filter by status' }, ...getEnumOptions(UserStatus)]}
/>
)
}
4 changes: 4 additions & 0 deletions libs/web/user/ui/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
export * from './lib/admin-user-ui-create-form'
export * from './lib/admin-user-ui-search'
export * from './lib/admin-user-ui-table'
export * from './lib/admin-user-ui-update-form'
export * from './lib/user-ui-autocomplete'
export * from './lib/user-ui-avatar'
export * from './lib/user-ui-profile'
export * from './lib/user-ui-role-badge'
export * from './lib/user-ui-search'
export * from './lib/user-ui-status-badge'
export * from './lib/user-ui-title'
export * from './lib/user-ui-update-form'
12 changes: 12 additions & 0 deletions libs/web/user/ui/src/lib/admin-user-ui-search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useAdminFindManyUser } from '@pubkey-stack/web-user-data-access'
import { UserUiAutocomplete, type UserUiAutocompleteProps } from './user-ui-autocomplete'

export type AdminUserUiSearchProps = Omit<UserUiAutocompleteProps, 'items' | 'isLoading' | 'setSearch'>

export function AdminUserUiSearch({ select, ...props }: AdminUserUiSearchProps) {
const { items, query, setSearch } = useAdminFindManyUser({ limit: 5 })

return (
<UserUiAutocomplete isLoading={query.isLoading} select={select} items={items} setSearch={setSearch} {...props} />
)
}
26 changes: 3 additions & 23 deletions libs/web/user/ui/src/lib/admin-user-ui-table.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { ActionIcon, Anchor, Group, ScrollArea, Stack, Text } from '@mantine/core'
import { ActionIcon, Group, ScrollArea } from '@mantine/core'
import { User } from '@pubkey-stack/sdk'
import { IdentityUiAvatarGroup } from '@pubkey-stack/web-identity-ui'
import { UiGroup } from '@pubkey-ui/core'
import { IconPencil, IconTrash, IconUser } from '@tabler/icons-react'
import { DataTable, DataTableProps } from 'mantine-datatable'
import { Link } from 'react-router-dom'
import { UserUiAvatar } from './user-ui-avatar'
import { UserUiRoleBadge } from './user-ui-role-badge'
import { UserUiStatusBadge } from './user-ui-status-badge'
import { UserUiTitle } from './user-ui-title'

interface AdminUserTableProps {
users: User[]
Expand Down Expand Up @@ -39,26 +38,7 @@ export function AdminUserUiTable({
columns={[
{
accessor: 'username',
render: (item) => {
const link = `/admin/users/${item.id}`
return (
<Group gap="sm" p={4}>
<UserUiAvatar size={40} user={item} radius={50} />
<Stack gap={1}>
<UiGroup justify="left" gap={4} align="baseline">
<Anchor component={Link} to={link} size="sm" fw={500}>
{item.username}
</Anchor>
</UiGroup>
{item.name ? (
<Text component={Link} to={link} size="sm" color="dimmed">
{item.name}
</Text>
) : null}
</Stack>
</Group>
)
},
render: (item) => <UserUiTitle user={item} to={`/admin/users/${item.id}`} />,
},
{
accessor: 'identities',
Expand Down
Loading

0 comments on commit 7f754f9

Please sign in to comment.