feat: add "on this page" to empty table message when you're past page 1 (#4886)

* Use empty page message on workspaces page

* Add prop for story

* AuditPage

* UsersPage

* Lint and format

* Fix tests

* Remove log

* Try to fix story

* Fix the right story
This commit is contained in:
Presley Pizzo
2022-11-08 11:08:51 -05:00
committed by GitHub
parent f496b149df
commit 87b3fe1afb
23 changed files with 355 additions and 222 deletions

View File

@ -40,7 +40,7 @@ export const ChooseOne = ({
}
if (conditionedOptions.some((cond) => cond.props.condition === undefined)) {
throw new Error(
"A non-final Cond in a ChooseOne does not have a condition prop.",
"A non-final Cond in a ChooseOne does not have a condition prop or the prop is undefined.",
)
}
const chosen = conditionedOptions.find((child) => child.props.condition)

View File

@ -102,3 +102,9 @@ export const createPaginationRef = (
): PaginationMachineRef => {
return spawn(paginationMachine.withContext(context))
}
export const nonInitialPage = (searchParams: URLSearchParams): boolean => {
const page = searchParams.get("page")
const numberPage = page ? Number(page) : 1
return numberPage > 1
}

View File

@ -1,6 +1,6 @@
import { ComponentMeta, Story } from "@storybook/react"
import {
MockSiteRoles,
MockAssignableSiteRoles,
MockUser,
MockUser2,
} from "../../testHelpers/renderHelpers"
@ -9,6 +9,11 @@ import { UsersTable, UsersTableProps } from "./UsersTable"
export default {
title: "components/UsersTable",
component: UsersTable,
argTypes: {
isNonInitialPage: {
defaultValue: false,
},
},
} as ComponentMeta<typeof UsersTable>
const Template: Story<UsersTableProps> = (args) => <UsersTable {...args} />
@ -16,27 +21,27 @@ const Template: Story<UsersTableProps> = (args) => <UsersTable {...args} />
export const Example = Template.bind({})
Example.args = {
users: [MockUser, MockUser2],
roles: MockSiteRoles,
roles: MockAssignableSiteRoles,
canEditUsers: false,
}
export const Editable = Template.bind({})
Editable.args = {
users: [MockUser, MockUser2],
roles: MockSiteRoles,
roles: MockAssignableSiteRoles,
canEditUsers: true,
}
export const Empty = Template.bind({})
Empty.args = {
users: [],
roles: MockSiteRoles,
roles: MockAssignableSiteRoles,
}
export const Loading = Template.bind({})
Loading.args = {
users: [],
roles: MockSiteRoles,
roles: MockAssignableSiteRoles,
isLoading: true,
}
Loading.parameters = {

View File

@ -15,6 +15,7 @@ describe("AuditPage", () => {
onActivateUser={() => jest.fn()}
onResetUserPassword={() => jest.fn()}
onUpdateUserRoles={() => jest.fn()}
isNonInitialPage={false}
/>,
)

View File

@ -32,6 +32,7 @@ export interface UsersTableProps {
user: TypesGen.User,
roles: TypesGen.Role["name"][],
) => void
isNonInitialPage: boolean
}
export const UsersTable: FC<React.PropsWithChildren<UsersTableProps>> = ({
@ -46,6 +47,7 @@ export const UsersTable: FC<React.PropsWithChildren<UsersTableProps>> = ({
isUpdatingUserRoles,
canEditUsers,
isLoading,
isNonInitialPage,
}) => {
return (
<TableContainer>
@ -78,6 +80,7 @@ export const UsersTable: FC<React.PropsWithChildren<UsersTableProps>> = ({
onResetUserPassword={onResetUserPassword}
onSuspendUser={onSuspendUser}
onUpdateUserRoles={onUpdateUserRoles}
isNonInitialPage={isNonInitialPage}
/>
</TableBody>
</Table>

View File

@ -2,8 +2,10 @@ import Box from "@material-ui/core/Box"
import { makeStyles } from "@material-ui/core/styles"
import TableCell from "@material-ui/core/TableCell"
import TableRow from "@material-ui/core/TableRow"
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
import { LastUsed } from "components/LastUsed/LastUsed"
import { FC } from "react"
import { useTranslation } from "react-i18next"
import * as TypesGen from "../../api/typesGenerated"
import { combineClasses } from "../../util/combineClasses"
import { AvatarData } from "../AvatarData/AvatarData"
@ -12,15 +14,6 @@ import { RoleSelect } from "../RoleSelect/RoleSelect"
import { TableLoader } from "../TableLoader/TableLoader"
import { TableRowMenu } from "../TableRowMenu/TableRowMenu"
export const Language = {
emptyMessage: "No users found",
suspendMenuItem: "Suspend",
deleteMenuItem: "Delete",
listWorkspacesMenuItem: "View workspaces",
activateMenuItem: "Activate",
resetPasswordMenuItem: "Reset password",
}
interface UsersTableBodyProps {
users?: TypesGen.User[]
roles?: TypesGen.AssignableRoles[]
@ -36,6 +29,7 @@ interface UsersTableBodyProps {
user: TypesGen.User,
roles: TypesGen.Role["name"][],
) => void
isNonInitialPage: boolean
}
export const UsersTableBody: FC<
@ -52,121 +46,144 @@ export const UsersTableBody: FC<
isUpdatingUserRoles,
canEditUsers,
isLoading,
isNonInitialPage,
}) => {
const styles = useStyles()
if (isLoading) {
return <TableLoader />
}
if (!users || users.length === 0) {
return (
<TableRow>
<TableCell colSpan={999}>
<Box p={4}>
<EmptyState message={Language.emptyMessage} />
</Box>
</TableCell>
</TableRow>
)
}
const { t } = useTranslation("usersPage")
return (
<>
{users.map((user) => {
// When the user has no role we want to show they are a Member
const fallbackRole: TypesGen.Role = {
name: "member",
display_name: "Member",
}
const userRoles = user.roles.length === 0 ? [fallbackRole] : user.roles
return (
<TableRow key={user.id}>
<TableCell>
<AvatarData
title={user.username}
subtitle={user.email}
highlightTitle
avatar={
user.avatar_url ? (
<img
className={styles.avatar}
alt={`${user.username}'s Avatar`}
src={user.avatar_url}
/>
) : null
}
/>
</TableCell>
<TableCell
className={combineClasses([
styles.status,
user.status === "suspended" ? styles.suspended : undefined,
])}
>
{user.status}
</TableCell>
<TableCell>
<LastUsed lastUsedAt={user.last_seen_at} />
</TableCell>
<TableCell>
{canEditUsers ? (
<RoleSelect
roles={roles ?? []}
selectedRoles={userRoles}
loading={isUpdatingUserRoles}
onChange={(roles) => {
// Remove the fallback role because it is only for the UI
roles = roles.filter((role) => role !== fallbackRole.name)
onUpdateUserRoles(user, roles)
}}
/>
) : (
<>{userRoles.map((role) => role.display_name).join(", ")}</>
)}
</TableCell>
{canEditUsers && (
<TableCell>
<TableRowMenu
data={user}
menuItems={
// Return either suspend or activate depending on status
(user.status === "active"
? [
{
label: Language.suspendMenuItem,
onClick: onSuspendUser,
},
]
: [
{
label: Language.activateMenuItem,
onClick: onActivateUser,
},
]
).concat(
{
label: Language.deleteMenuItem,
onClick: onDeleteUser,
},
{
label: Language.listWorkspacesMenuItem,
onClick: onListWorkspaces,
},
{
label: Language.resetPasswordMenuItem,
onClick: onResetUserPassword,
},
)
}
/>
<ChooseOne>
<Cond condition={Boolean(isLoading)}>
<TableLoader />
</Cond>
<Cond condition={!users || users.length === 0}>
<ChooseOne>
<Cond condition={isNonInitialPage}>
<TableRow>
<TableCell colSpan={999}>
<Box p={4}>
<EmptyState message={t("emptyPageMessage")} />
</Box>
</TableCell>
)}
</TableRow>
)
})}
</>
</TableRow>
</Cond>
<Cond>
<TableRow>
<TableCell colSpan={999}>
<Box p={4}>
<EmptyState message={t("emptyMessage")} />
</Box>
</TableCell>
</TableRow>
</Cond>
</ChooseOne>
</Cond>
<Cond>
<>
{users &&
users.map((user) => {
// When the user has no role we want to show they are a Member
const fallbackRole: TypesGen.Role = {
name: "member",
display_name: "Member",
}
const userRoles =
user.roles.length === 0 ? [fallbackRole] : user.roles
return (
<TableRow key={user.id}>
<TableCell>
<AvatarData
title={user.username}
subtitle={user.email}
highlightTitle
avatar={
user.avatar_url ? (
<img
className={styles.avatar}
alt={`${user.username}'s Avatar`}
src={user.avatar_url}
/>
) : null
}
/>
</TableCell>
<TableCell
className={combineClasses([
styles.status,
user.status === "suspended"
? styles.suspended
: undefined,
])}
>
{user.status}
</TableCell>
<TableCell>
<LastUsed lastUsedAt={user.last_seen_at} />
</TableCell>
<TableCell>
{canEditUsers ? (
<RoleSelect
roles={roles ?? []}
selectedRoles={userRoles}
loading={isUpdatingUserRoles}
onChange={(roles) => {
// Remove the fallback role because it is only for the UI
roles = roles.filter(
(role) => role !== fallbackRole.name,
)
onUpdateUserRoles(user, roles)
}}
/>
) : (
<>
{userRoles.map((role) => role.display_name).join(", ")}
</>
)}
</TableCell>
{canEditUsers && (
<TableCell>
<TableRowMenu
data={user}
menuItems={
// Return either suspend or activate depending on status
(user.status === "active"
? [
{
label: t("suspendMenuItem"),
onClick: onSuspendUser,
},
]
: [
{
label: t("activateMenuItem"),
onClick: onActivateUser,
},
]
).concat(
{
label: t("deleteMenuItem"),
onClick: onDeleteUser,
},
{
label: t("listWorkspacesMenuItem"),
onClick: onListWorkspaces,
},
{
label: t("resetPasswordMenuItem"),
onClick: onResetUserPassword,
},
)
}
/>
</TableCell>
)}
</TableRow>
)
})}
</>
</Cond>
</ChooseOne>
)
}

View File

@ -21,11 +21,12 @@ export interface WorkspacesTableProps {
isLoading?: boolean
workspaceRefs?: WorkspaceItemMachineRef[]
filter?: string
isNonInitialPage: boolean
}
export const WorkspacesTable: FC<
React.PropsWithChildren<WorkspacesTableProps>
> = ({ isLoading, workspaceRefs, filter }) => {
> = ({ isLoading, workspaceRefs, filter, isNonInitialPage }) => {
return (
<TableContainer>
<Table>
@ -44,6 +45,7 @@ export const WorkspacesTable: FC<
isLoading={isLoading}
workspaceRefs={workspaceRefs}
filter={filter}
isNonInitialPage={isNonInitialPage}
/>
</TableBody>
</Table>

View File

@ -3,7 +3,9 @@ import Link from "@material-ui/core/Link"
import TableCell from "@material-ui/core/TableCell"
import TableRow from "@material-ui/core/TableRow"
import AddCircleOutline from "@material-ui/icons/AddCircleOutline"
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
import { FC } from "react"
import { useTranslation } from "react-i18next"
import { Link as RouterLink } from "react-router-dom"
import { workspaceFilterQuery } from "../../util/filters"
import { WorkspaceItemMachineRef } from "../../xServices/workspaces/workspacesXService"
@ -11,63 +13,65 @@ import { EmptyState } from "../EmptyState/EmptyState"
import { TableLoader } from "../TableLoader/TableLoader"
import { WorkspacesRow } from "./WorkspacesRow"
export const Language = {
emptyCreateWorkspaceMessage: "Create your first workspace",
emptyCreateWorkspaceDescription:
"Start editing your source code and building your software.",
createFromTemplateButton: "Create from template",
emptyResultsMessage: "No results matched your search",
}
interface TableBodyProps {
isLoading?: boolean
workspaceRefs?: WorkspaceItemMachineRef[]
filter?: string
isNonInitialPage: boolean
}
export const WorkspacesTableBody: FC<
React.PropsWithChildren<TableBodyProps>
> = ({ isLoading, workspaceRefs, filter }) => {
if (isLoading) {
return <TableLoader />
}
if (!workspaceRefs || workspaceRefs.length === 0) {
return (
<>
{filter === workspaceFilterQuery.me ||
filter === workspaceFilterQuery.all ? (
<TableRow>
<TableCell colSpan={999}>
<EmptyState
message={Language.emptyCreateWorkspaceMessage}
description={Language.emptyCreateWorkspaceDescription}
cta={
<Link underline="none" component={RouterLink} to="/templates">
<Button startIcon={<AddCircleOutline />}>
{Language.createFromTemplateButton}
</Button>
</Link>
}
/>
</TableCell>
</TableRow>
) : (
<TableRow>
<TableCell colSpan={999}>
<EmptyState message={Language.emptyResultsMessage} />
</TableCell>
</TableRow>
)}
</>
)
}
> = ({ isLoading, workspaceRefs, filter, isNonInitialPage }) => {
const { t } = useTranslation("workspacesPage")
return (
<>
{workspaceRefs.map((workspaceRef) => (
<WorkspacesRow workspaceRef={workspaceRef} key={workspaceRef.id} />
))}
</>
<ChooseOne>
<Cond condition={Boolean(isLoading)}>
<TableLoader />
</Cond>
<Cond condition={!workspaceRefs || workspaceRefs.length === 0}>
<TableRow>
<TableCell colSpan={999}>
<ChooseOne>
<Cond condition={isNonInitialPage}>
<EmptyState message={t("emptyPageMessage")} />
</Cond>
<Cond
condition={
filter === workspaceFilterQuery.me ||
filter === workspaceFilterQuery.all
}
>
<EmptyState
message={t("emptyCreateWorkspaceMessage")}
description={t("emptyCreateWorkspaceDescription")}
cta={
<Link
underline="none"
component={RouterLink}
to="/templates"
>
<Button startIcon={<AddCircleOutline />}>
{t("createFromTemplateButton")}
</Button>
</Link>
}
/>
</Cond>
<Cond>
<EmptyState message={t("emptyResultsMessage")} />
</Cond>
</ChooseOne>
</TableCell>
</TableRow>
</Cond>
<Cond>
{workspaceRefs &&
workspaceRefs.map((workspaceRef) => (
<WorkspacesRow workspaceRef={workspaceRef} key={workspaceRef.id} />
))}
</Cond>
</ChooseOne>
)
}

View File

@ -3,5 +3,9 @@
"create": "created a new",
"write": "updated",
"delete": "deleted"
},
"table": {
"emptyPage": "No audit logs available on this page",
"noLogs": "No audit logs available"
}
}

View File

@ -6,6 +6,8 @@ import templatesPage from "./templatesPage.json"
import workspacePage from "./workspacePage.json"
import agent from "./agent.json"
import buildPage from "./buildPage.json"
import workspacesPage from "./workspacesPage.json"
import usersPage from "./usersPage.json"
export const en = {
common,
@ -16,4 +18,6 @@ export const en = {
createWorkspacePage,
agent,
buildPage,
workspacesPage,
usersPage,
}

View File

@ -0,0 +1,9 @@
{
"emptyMessage": "No users found",
"emptyPageMessage": "No users found on this page",
"suspendMenuItem": "Suspend",
"deleteMenuItem": "Delete",
"listWorkspacesMenuItem": "View workspaces",
"activateMenuItem": "Activate",
"resetPasswordMenuItem": "Reset password"
}

View File

@ -0,0 +1,7 @@
{
"emptyCreateWorkspaceMessage": "Create your first workspace",
"emptyCreateWorkspaceDescription": "Start editing your source code and building your software.",
"createFromTemplateButton": "Create from template",
"emptyResultsMessage": "No results matched your search",
"emptyPageMessage": "No results on this page"
}

View File

@ -1,5 +1,8 @@
import { useMachine } from "@xstate/react"
import { getPaginationContext } from "components/PaginationWidget/utils"
import {
getPaginationContext,
nonInitialPage,
} from "components/PaginationWidget/utils"
import { FC } from "react"
import { Helmet } from "react-helmet-async"
import { useSearchParams } from "react-router-dom"
@ -38,6 +41,7 @@ const AuditPage: FC = () => {
auditSend("FILTER", { filter })
}}
paginationRef={paginationRef}
isNonInitialPage={nonInitialPage(searchParams)}
/>
</>
)

View File

@ -25,6 +25,26 @@ const Template: Story<AuditPageViewProps> = (args) => (
export const AuditPage = Template.bind({})
export const Loading = Template.bind({})
Loading.args = {
auditLogs: undefined,
count: undefined,
isNonInitialPage: false,
}
export const EmptyPage = Template.bind({})
EmptyPage.args = {
auditLogs: [],
isNonInitialPage: true,
}
export const NoLogs = Template.bind({})
NoLogs.args = {
auditLogs: [],
count: 0,
isNonInitialPage: false,
}
export const AuditPageSmallViewport = Template.bind({})
AuditPageSmallViewport.parameters = {
chromatic: { viewports: [600] },

View File

@ -5,6 +5,7 @@ import TableContainer from "@material-ui/core/TableContainer"
import TableRow from "@material-ui/core/TableRow"
import { AuditLog } from "api/typesGenerated"
import { AuditLogRow } from "components/AuditLogRow/AuditLogRow"
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
import { EmptyState } from "components/EmptyState/EmptyState"
import { Margins } from "components/Margins/Margins"
import {
@ -19,6 +20,7 @@ import { TableLoader } from "components/TableLoader/TableLoader"
import { Timeline } from "components/Timeline/Timeline"
import { AuditHelpTooltip } from "components/Tooltips"
import { FC } from "react"
import { useTranslation } from "react-i18next"
import { PaginationMachineRef } from "xServices/pagination/paginationXService"
export const Language = {
@ -43,6 +45,7 @@ export interface AuditPageViewProps {
filter: string
onFilter: (filter: string) => void
paginationRef: PaginationMachineRef
isNonInitialPage: boolean
}
export const AuditPageView: FC<AuditPageViewProps> = ({
@ -51,7 +54,9 @@ export const AuditPageView: FC<AuditPageViewProps> = ({
filter,
onFilter,
paginationRef,
isNonInitialPage,
}) => {
const { t } = useTranslation("auditLog")
const isLoading = auditLogs === undefined || count === undefined
const isEmpty = !isLoading && auditLogs.length === 0
@ -77,23 +82,38 @@ export const AuditPageView: FC<AuditPageViewProps> = ({
<TableContainer>
<Table>
<TableBody>
{isLoading && <TableLoader />}
{auditLogs && (
<Timeline
items={auditLogs}
getDate={(log) => new Date(log.time)}
row={(log) => <AuditLogRow key={log.id} auditLog={log} />}
/>
)}
{isEmpty && (
<TableRow>
<TableCell colSpan={999}>
<EmptyState message="No audit logs available" />
</TableCell>
</TableRow>
)}
<ChooseOne>
<Cond condition={isLoading}>
<TableLoader />
</Cond>
<Cond condition={isEmpty}>
<ChooseOne>
<Cond condition={isNonInitialPage}>
<TableRow>
<TableCell colSpan={999}>
<EmptyState message={t("table.emptyPage")} />
</TableCell>
</TableRow>
</Cond>
<Cond>
<TableRow>
<TableCell colSpan={999}>
<EmptyState message={t("table.noLogs")} />
</TableCell>
</TableRow>
</Cond>
</ChooseOne>
</Cond>
<Cond>
{auditLogs && (
<Timeline
items={auditLogs}
getDate={(log) => new Date(log.time)}
row={(log) => <AuditLogRow key={log.id} auditLog={log} />}
/>
)}
</Cond>
</ChooseOne>
</TableBody>
</Table>
</TableContainer>

View File

@ -8,7 +8,6 @@ import { Role } from "../../api/typesGenerated"
import { Language as ResetPasswordDialogLanguage } from "../../components/Dialogs/ResetPasswordDialog/ResetPasswordDialog"
import { GlobalSnackbar } from "../../components/GlobalSnackbar/GlobalSnackbar"
import { Language as RoleSelectLanguage } from "../../components/RoleSelect/RoleSelect"
import { Language as UsersTableBodyLanguage } from "../../components/UsersTable/UsersTableBody"
import {
MockAuditorRole,
MockUser,
@ -39,9 +38,8 @@ const suspendUser = async (setupActionSpies: () => void) => {
await user.click(firstMoreButton)
const menu = await screen.findByRole("menu")
const suspendButton = within(menu).getByText(
UsersTableBodyLanguage.suspendMenuItem,
)
const text = t("suspendMenuItem", { ns: "usersPage" })
const suspendButton = within(menu).getByText(text)
await user.click(suspendButton)
@ -72,9 +70,8 @@ const deleteUser = async (setupActionSpies: () => void) => {
await user.click(selectedMoreButton)
const menu = await screen.findByRole("menu")
const deleteButton = within(menu).getByText(
UsersTableBodyLanguage.deleteMenuItem,
)
const text = t("deleteMenuItem", { ns: "usersPage" })
const deleteButton = within(menu).getByText(text)
await user.click(deleteButton)
@ -107,9 +104,8 @@ const activateUser = async (setupActionSpies: () => void) => {
fireEvent.click(suspendedMoreButton)
const menu = screen.getByRole("menu")
const activateButton = within(menu).getByText(
UsersTableBodyLanguage.activateMenuItem,
)
const text = t("activateMenuItem", { ns: "usersPage" })
const activateButton = within(menu).getByText(text)
fireEvent.click(activateButton)
// Check if the confirm message is displayed
@ -135,9 +131,8 @@ const resetUserPassword = async (setupActionSpies: () => void) => {
fireEvent.click(firstMoreButton)
const menu = screen.getByRole("menu")
const resetPasswordButton = within(menu).getByText(
UsersTableBodyLanguage.resetPasswordMenuItem,
)
const text = t("resetPasswordMenuItem", { ns: "usersPage" })
const resetPasswordButton = within(menu).getByText(text)
fireEvent.click(resetPasswordButton)

View File

@ -2,7 +2,10 @@ import { useActor, useMachine } from "@xstate/react"
import { getErrorDetail } from "api/errors"
import { User } from "api/typesGenerated"
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog"
import { getPaginationContext } from "components/PaginationWidget/utils"
import {
getPaginationContext,
nonInitialPage,
} from "components/PaginationWidget/utils"
import { usePermissions } from "hooks/usePermissions"
import { FC, ReactNode, useContext, useEffect } from "react"
import { Helmet } from "react-helmet-async"
@ -135,6 +138,7 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
usersSend({ type: "UPDATE_FILTER", query })
}}
paginationRef={paginationRef}
isNonInitialPage={nonInitialPage(searchParams)}
/>
<DeleteDialog

View File

@ -14,6 +14,18 @@ export default {
paginationRef: {
defaultValue: createPaginationRef({ page: 1, limit: 25 }),
},
isNonInitialPage: {
defaultValue: false,
},
users: {
defaultValue: [MockUser, MockUser2],
},
roles: {
defaultValue: MockAssignableSiteRoles,
},
canEditUsers: {
defaultValue: true,
},
},
} as ComponentMeta<typeof UsersPageView>
@ -22,29 +34,23 @@ const Template: Story<UsersPageViewProps> = (args) => (
)
export const Admin = Template.bind({})
Admin.args = {
users: [MockUser, MockUser2],
roles: MockAssignableSiteRoles,
canEditUsers: true,
}
export const SmallViewport = Template.bind({})
SmallViewport.args = {
...Admin.args,
}
SmallViewport.parameters = {
chromatic: { viewports: [600] },
}
export const Member = Template.bind({})
Member.args = { ...Admin.args, canEditUsers: false }
Member.args = { canEditUsers: false }
export const Empty = Template.bind({})
Empty.args = { ...Admin.args, users: [] }
Empty.args = { users: [] }
export const EmptyPage = Template.bind({})
EmptyPage.args = { users: [], isNonInitialPage: true }
export const Error = Template.bind({})
Error.args = {
...Admin.args,
users: undefined,
error: {
response: {

View File

@ -30,6 +30,7 @@ export interface UsersPageViewProps {
) => void
onFilter: (query: string) => void
paginationRef: PaginationMachineRef
isNonInitialPage: boolean
}
export const UsersPageView: FC<React.PropsWithChildren<UsersPageViewProps>> = ({
@ -49,6 +50,7 @@ export const UsersPageView: FC<React.PropsWithChildren<UsersPageViewProps>> = ({
filter,
onFilter,
paginationRef,
isNonInitialPage,
}) => {
const presetFilters = [
{ query: userFilterQuery.active, name: Language.activeUsersFilterName },
@ -76,6 +78,7 @@ export const UsersPageView: FC<React.PropsWithChildren<UsersPageViewProps>> = ({
isUpdatingUserRoles={isUpdatingUserRoles}
canEditUsers={canEditUsers}
isLoading={isLoading}
isNonInitialPage={isNonInitialPage}
/>
<PaginationWidget numRecords={count} paginationRef={paginationRef} />

View File

@ -1,11 +1,13 @@
import { screen, waitFor } from "@testing-library/react"
import { rest } from "msw"
import * as CreateDayString from "util/createDayString"
import { Language as WorkspacesTableBodyLanguage } from "../../components/WorkspacesTable/WorkspacesTableBody"
import { MockWorkspace } from "../../testHelpers/entities"
import { history, render } from "../../testHelpers/renderHelpers"
import { server } from "../../testHelpers/server"
import WorkspacesPage from "./WorkspacesPage"
import { i18n } from "i18n"
const { t } = i18n
describe("WorkspacesPage", () => {
beforeEach(() => {
@ -27,9 +29,8 @@ describe("WorkspacesPage", () => {
render(<WorkspacesPage />)
// Then
await screen.findByText(
WorkspacesTableBodyLanguage.emptyCreateWorkspaceMessage,
)
const text = t("emptyCreateWorkspaceMessage", { ns: "workspacesPage" })
await screen.findByText(text)
})
it("renders a filled workspaces page", async () => {

View File

@ -1,5 +1,8 @@
import { useMachine } from "@xstate/react"
import { getPaginationContext } from "components/PaginationWidget/utils"
import {
getPaginationContext,
nonInitialPage,
} from "components/PaginationWidget/utils"
import { FC } from "react"
import { Helmet } from "react-helmet-async"
import { useSearchParams } from "react-router-dom"
@ -49,6 +52,7 @@ const WorkspacesPage: FC = () => {
})
}}
paginationRef={paginationRef}
isNonInitialPage={nonInitialPage(searchParams)}
/>
</>
)

View File

@ -112,6 +112,7 @@ AllStates.args = {
...Object.values(additionalWorkspaces),
],
count: 14,
isNonInitialPage: false,
}
export const OwnerHasNoWorkspaces = Template.bind({})
@ -119,6 +120,7 @@ OwnerHasNoWorkspaces.args = {
workspaceRefs: [],
filter: workspaceFilterQuery.me,
count: 0,
isNonInitialPage: false,
}
export const NoResults = Template.bind({})
@ -126,4 +128,13 @@ NoResults.args = {
workspaceRefs: [],
filter: "searchtearmwithnoresults",
count: 0,
isNonInitialPage: false,
}
export const EmptyPage = Template.bind({})
EmptyPage.args = {
workspaceRefs: [],
filter: workspaceFilterQuery.me,
count: 0,
isNonInitialPage: true,
}

View File

@ -36,6 +36,7 @@ export interface WorkspacesPageViewProps {
filter?: string
onFilter: (query: string) => void
paginationRef: PaginationMachineRef
isNonInitialPage: boolean
}
export const WorkspacesPageView: FC<
@ -49,6 +50,7 @@ export const WorkspacesPageView: FC<
filter,
onFilter,
paginationRef,
isNonInitialPage,
}) => {
const presetFilters = [
{ query: workspaceFilterQuery.me, name: Language.yourWorkspacesButton },
@ -105,6 +107,7 @@ export const WorkspacesPageView: FC<
isLoading={isLoading}
workspaceRefs={workspaceRefs}
filter={filter}
isNonInitialPage={isNonInitialPage}
/>
<PaginationWidget numRecords={count} paginationRef={paginationRef} />