Skip to content

Commit 82275a8

Browse files
presleypgreyscaled
andauthored
feat(site): Read users into basic UsersTable (#981)
* Start users * Set up fake response * Update handler * Update types * Set up page * Start adding table * Add header * Add Header * Remove roles * Add UsersPageView * Add test * Lint * Storybook error summary * Strip Pager to just what's currently needed * Clean up ErrorSummary while I'm here * Storybook tweaks * Extract language * Lint * Add missing $ Co-authored-by: G r e y <grey@coder.com> * Lint * Lint * Fix syntax error * Lint Co-authored-by: G r e y <grey@coder.com>
1 parent f803e37 commit 82275a8

File tree

17 files changed

+287
-14
lines changed

17 files changed

+287
-14
lines changed

site/src/AppRouter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { SettingsPage } from "./pages/settings"
1616
import { TemplatesPage } from "./pages/templates"
1717
import { TemplatePage } from "./pages/templates/[organization]/[template]"
1818
import { CreateWorkspacePage } from "./pages/templates/[organization]/[template]/create"
19-
import { UsersPage } from "./pages/users"
19+
import { UsersPage } from "./pages/UsersPage/UsersPage"
2020
import { WorkspacePage } from "./pages/workspaces/[workspace]"
2121

2222
export const AppRouter: React.FC = () => (

site/src/api/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import axios, { AxiosRequestHeaders } from "axios"
22
import { mutate } from "swr"
3+
import { MockPager, MockUser, MockUser2 } from "../test_helpers"
34
import * as Types from "./types"
45

56
const CONTENT_TYPE_JSON: AxiosRequestHeaders = {
@@ -69,6 +70,15 @@ export const getApiKey = async (): Promise<Types.APIKeyResponse> => {
6970
return response.data
7071
}
7172

73+
export const getUsers = async (): Promise<Types.PagedUsers> => {
74+
// const response = await axios.get<Types.UserResponse[]>("/api/v2/users")
75+
// return response.data
76+
return Promise.resolve({
77+
page: [MockUser, MockUser2],
78+
pager: MockPager,
79+
})
80+
}
81+
7282
export const getBuildInfo = async (): Promise<Types.BuildInfoResponse> => {
7383
const response = await axios.get("/api/v2/buildinfo")
7484
return response.data

site/src/api/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ export interface UserAgent {
7979
readonly os: string
8080
}
8181

82+
export interface Pager {
83+
total: number
84+
}
85+
86+
export interface PagedUsers {
87+
page: UserResponse[]
88+
pager: Pager
89+
}
90+
8291
export interface WorkspaceAutostartRequest {
8392
schedule: string
8493
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { ComponentMeta, Story } from "@storybook/react"
2+
import React from "react"
3+
import { ErrorSummary, ErrorSummaryProps } from "."
4+
5+
export default {
6+
title: "components/ErrorSummary",
7+
component: ErrorSummary,
8+
} as ComponentMeta<typeof ErrorSummary>
9+
10+
const Template: Story<ErrorSummaryProps> = (args) => <ErrorSummary {...args} />
11+
12+
export const WithError = Template.bind({})
13+
WithError.args = {
14+
error: new Error("Something went wrong!"),
15+
}
16+
17+
export const WithUndefined = Template.bind({})
Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import React from "react"
22

3+
const Language = {
4+
unknownErrorMessage: "Unknown error",
5+
}
6+
37
export interface ErrorSummaryProps {
4-
error: Error | undefined
8+
error: Error | unknown
59
}
610

711
export const ErrorSummary: React.FC<ErrorSummaryProps> = ({ error }) => {
812
// TODO: More interesting error page
913

10-
if (typeof error === "undefined") {
11-
return <div>{"Unknown error"}</div>
14+
if (!(error instanceof Error)) {
15+
return <div>{Language.unknownErrorMessage}</div>
16+
} else {
17+
return <div>{error.toString()}</div>
1218
}
13-
14-
return <div>{error.toString()}</div>
1519
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { ComponentMeta, Story } from "@storybook/react"
2+
import React from "react"
3+
import { MockUser, MockUser2 } from "../../test_helpers"
4+
import { UsersTable, UsersTableProps } from "./UsersTable"
5+
6+
export default {
7+
title: "Components/UsersTable",
8+
component: UsersTable,
9+
} as ComponentMeta<typeof UsersTable>
10+
11+
const Template: Story<UsersTableProps> = (args) => <UsersTable {...args} />
12+
13+
export const Example = Template.bind({})
14+
Example.args = {
15+
users: [MockUser, MockUser2],
16+
}
17+
18+
export const Empty = Template.bind({})
19+
Empty.args = {
20+
users: [],
21+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from "react"
2+
import { UserResponse } from "../../api/types"
3+
import { Column, Table } from "../../components/Table"
4+
import { EmptyState } from "../EmptyState"
5+
import { UserCell } from "../Table/Cells/UserCell"
6+
7+
const Language = {
8+
pageTitle: "Users",
9+
usersTitle: "All users",
10+
emptyMessage: "No users found",
11+
usernameLabel: "User",
12+
}
13+
14+
const emptyState = <EmptyState message={Language.emptyMessage} />
15+
16+
const columns: Column<UserResponse>[] = [
17+
{
18+
key: "username",
19+
name: Language.usernameLabel,
20+
renderer: (field, data) => {
21+
return <UserCell Avatar={{ username: data.username }} primaryText={data.username} caption={data.email} />
22+
},
23+
},
24+
]
25+
26+
export interface UsersTableProps {
27+
users: UserResponse[]
28+
}
29+
30+
export const UsersTable: React.FC<UsersTableProps> = ({ users }) => {
31+
return <Table columns={columns} data={users} title={Language.usersTitle} emptyState={emptyState} />
32+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { screen } from "@testing-library/react"
2+
import React from "react"
3+
import { MockPager, render } from "../../test_helpers"
4+
import { UsersPage } from "./UsersPage"
5+
import { Language } from "./UsersPageView"
6+
7+
describe("Users Page", () => {
8+
it("has a header with the total number of users", async () => {
9+
render(<UsersPage />)
10+
const total = await screen.findByText(/\d+ total/)
11+
expect(total.innerHTML).toEqual(Language.pageSubtitle(MockPager))
12+
})
13+
it("shows users", async () => {
14+
render(<UsersPage />)
15+
const users = await screen.findAllByText(/.*@coder.com/)
16+
expect(users.length).toEqual(2)
17+
})
18+
})
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useActor } from "@xstate/react"
2+
import React, { useContext } from "react"
3+
import { ErrorSummary } from "../../components/ErrorSummary"
4+
import { XServiceContext } from "../../xServices/StateContext"
5+
import { UsersPageView } from "./UsersPageView"
6+
7+
export const UsersPage: React.FC = () => {
8+
const xServices = useContext(XServiceContext)
9+
const [usersState] = useActor(xServices.usersXService)
10+
const { users, pager, getUsersError } = usersState.context
11+
12+
if (usersState.matches("error")) {
13+
return <ErrorSummary error={getUsersError} />
14+
} else {
15+
return <UsersPageView users={users} pager={pager} />
16+
}
17+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { ComponentMeta, Story } from "@storybook/react"
2+
import React from "react"
3+
import { MockPager, MockUser, MockUser2 } from "../../test_helpers"
4+
import { UsersPageView, UsersPageViewProps } from "./UsersPageView"
5+
6+
export default {
7+
title: "pages/UsersPageView",
8+
component: UsersPageView,
9+
} as ComponentMeta<typeof UsersPageView>
10+
11+
const Template: Story<UsersPageViewProps> = (args) => <UsersPageView {...args} />
12+
13+
export const Ready = Template.bind({})
14+
Ready.args = {
15+
users: [MockUser, MockUser2],
16+
pager: MockPager,
17+
}
18+
export const Empty = Template.bind({})
19+
Empty.args = {
20+
users: [],
21+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy