Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 50ad2f8

Browse files
refactor: Improve the load state for the list pages (#1428)
1 parent f970829 commit 50ad2f8

File tree

15 files changed

+413
-270
lines changed

15 files changed

+413
-270
lines changed

site/src/components/EmptyState/EmptyState.test.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ describe("EmptyState", () => {
2121
await screen.findByText("Friendly greeting")
2222
})
2323

24-
it("renders description component", async () => {
24+
it("renders cta component", async () => {
2525
// Given
26-
const description = <button title="Click me" />
26+
const cta = <button title="Click me" />
2727

2828
// When
29-
render(<EmptyState message="Hello, world" description={description} />)
29+
render(<EmptyState message="Hello, world" cta={cta} />)
3030

3131
// Then
3232
await screen.findByText("Hello, world")

site/src/components/EmptyState/EmptyState.tsx

+18-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import Box from "@material-ui/core/Box"
2-
import Button, { ButtonProps } from "@material-ui/core/Button"
32
import { makeStyles } from "@material-ui/core/styles"
43
import Typography from "@material-ui/core/Typography"
54
import React from "react"
@@ -8,8 +7,8 @@ export interface EmptyStateProps {
87
/** Text Message to display, placed inside Typography component */
98
message: string
109
/** Longer optional description to display below the message */
11-
description?: React.ReactNode
12-
button?: ButtonProps
10+
description?: string
11+
cta?: React.ReactNode
1312
}
1413

1514
/**
@@ -21,17 +20,22 @@ export interface EmptyStateProps {
2120
* that you can directly pass props through to to customize the shape and layout of it.
2221
*/
2322
export const EmptyState: React.FC<EmptyStateProps> = (props) => {
24-
const { message, description, button, ...boxProps } = props
23+
const { message, description, cta, ...boxProps } = props
2524
const styles = useStyles()
26-
const buttonClassName = `${styles.button} ${button && button.className ? button.className : ""}`
2725

2826
return (
2927
<Box className={styles.root} {...boxProps}>
30-
<Typography variant="h5" color="textSecondary" className={styles.header}>
31-
{message}
32-
</Typography>
33-
{description && <div className={styles.description}>{description}</div>}
34-
{button && <Button variant="contained" color="primary" {...button} className={buttonClassName} />}
28+
<div className={styles.header}>
29+
<Typography variant="h5" className={styles.title}>
30+
{message}
31+
</Typography>
32+
{description && (
33+
<Typography variant="body2" color="textSecondary" className={styles.description}>
34+
{description}
35+
</Typography>
36+
)}
37+
</div>
38+
{cta}
3539
</Box>
3640
)
3741
}
@@ -48,22 +52,13 @@ const useStyles = makeStyles(
4852
padding: theme.spacing(3),
4953
},
5054
header: {
55+
marginBottom: theme.spacing(3),
56+
},
57+
title: {
5158
fontWeight: 400,
5259
},
5360
description: {
54-
marginTop: theme.spacing(2),
55-
marginBottom: theme.spacing(1),
56-
color: theme.palette.text.secondary,
57-
fontSize: theme.typography.body2.fontSize,
58-
},
59-
button: {
60-
marginTop: theme.spacing(2),
61-
},
62-
icon: {
63-
fontSize: theme.typography.h2.fontSize,
64-
color: theme.palette.text.secondary,
65-
marginBottom: theme.spacing(1),
66-
opacity: 0.5,
61+
marginTop: theme.spacing(1),
6762
},
6863
}),
6964
{ name: "EmptyState" },

site/src/components/Loader/FullScreenLoader.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import CircularProgress from "@material-ui/core/CircularProgress"
22
import { makeStyles } from "@material-ui/core/styles"
33
import React from "react"
44

5-
export const useStyles = makeStyles(() => ({
5+
export const useStyles = makeStyles((theme) => ({
66
root: {
77
position: "absolute",
88
top: "0",
@@ -12,6 +12,7 @@ export const useStyles = makeStyles(() => ({
1212
display: "flex",
1313
justifyContent: "center",
1414
alignItems: "center",
15+
background: theme.palette.background.default,
1516
},
1617
}))
1718

site/src/components/NavbarView/NavbarView.stories.tsx

+3-20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Story } from "@storybook/react"
22
import React from "react"
3+
import { MockUser, MockUser2 } from "../../testHelpers/entities"
34
import { NavbarView, NavbarViewProps } from "./NavbarView"
45

56
export default {
@@ -14,33 +15,15 @@ const Template: Story<NavbarViewProps> = (args: NavbarViewProps) => <NavbarView
1415

1516
export const ForAdmin = Template.bind({})
1617
ForAdmin.args = {
17-
user: {
18-
id: "1",
19-
username: "Administrator",
20-
21-
created_at: "dawn",
22-
status: "active",
23-
organization_ids: [],
24-
roles: [],
25-
},
26-
displayAdminDropdown: true,
18+
user: MockUser,
2719
onSignOut: () => {
2820
return Promise.resolve()
2921
},
3022
}
3123

3224
export const ForMember = Template.bind({})
3325
ForMember.args = {
34-
user: {
35-
id: "1",
36-
username: "CathyCoder",
37-
38-
created_at: "dawn",
39-
status: "active",
40-
organization_ids: [],
41-
roles: [],
42-
},
43-
displayAdminDropdown: false,
26+
user: MockUser2,
4427
onSignOut: () => {
4528
return Promise.resolve()
4629
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import Box from "@material-ui/core/Box"
2+
import CircularProgress from "@material-ui/core/CircularProgress"
3+
import { makeStyles } from "@material-ui/core/styles"
4+
import TableCell from "@material-ui/core/TableCell"
5+
import TableRow from "@material-ui/core/TableRow"
6+
import React from "react"
7+
8+
export const TableLoader: React.FC = () => {
9+
const styles = useStyles()
10+
11+
return (
12+
<TableRow>
13+
<TableCell colSpan={999} className={styles.cell}>
14+
<Box p={4}>
15+
<CircularProgress size={26} />
16+
</Box>
17+
</TableCell>
18+
</TableRow>
19+
)
20+
}
21+
22+
const useStyles = makeStyles((theme) => ({
23+
cell: {
24+
textAlign: "center",
25+
height: theme.spacing(20),
26+
},
27+
}))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { ComponentMeta, Story } from "@storybook/react"
2+
import React from "react"
3+
import { MockOrganization, MockTemplate } from "../../testHelpers/entities"
4+
import { TemplatesTable, TemplatesTableProps } from "./TemplatesTable"
5+
6+
export default {
7+
title: "components/TemplatesTable",
8+
component: TemplatesTable,
9+
} as ComponentMeta<typeof TemplatesTable>
10+
11+
const Template: Story<TemplatesTableProps> = (args) => <TemplatesTable {...args} />
12+
13+
export const Example = Template.bind({})
14+
Example.args = {
15+
templates: [MockTemplate],
16+
organizations: [MockOrganization],
17+
}
18+
19+
export const Empty = Template.bind({})
20+
Empty.args = {
21+
templates: [],
22+
organizations: [],
23+
}
24+
25+
export const Loading = Template.bind({})
26+
Loading.args = {
27+
templates: undefined,
28+
organizations: [],
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import Box from "@material-ui/core/Box"
2+
import Table from "@material-ui/core/Table"
3+
import TableBody from "@material-ui/core/TableBody"
4+
import TableCell from "@material-ui/core/TableCell"
5+
import TableHead from "@material-ui/core/TableHead"
6+
import TableRow from "@material-ui/core/TableRow"
7+
import React from "react"
8+
import { Link } from "react-router-dom"
9+
import * as TypesGen from "../../api/typesGenerated"
10+
import { CodeExample } from "../../components/CodeExample/CodeExample"
11+
import { EmptyState } from "../../components/EmptyState/EmptyState"
12+
import { TableHeaderRow } from "../../components/TableHeaders/TableHeaders"
13+
import { TableLoader } from "../../components/TableLoader/TableLoader"
14+
import { TableTitle } from "../../components/TableTitle/TableTitle"
15+
16+
export const Language = {
17+
title: "Templates",
18+
tableTitle: "All templates",
19+
nameLabel: "Name",
20+
emptyMessage: "No templates have been created yet",
21+
emptyDescription: "Run the following command to get started:",
22+
totalLabel: "total",
23+
}
24+
25+
export interface TemplatesTableProps {
26+
templates?: TypesGen.Template[]
27+
organizations?: TypesGen.Organization[]
28+
}
29+
30+
export const TemplatesTable: React.FC<TemplatesTableProps> = ({ templates, organizations }) => {
31+
const isLoading = !templates || !organizations
32+
33+
// Create a dictionary of organization ID -> organization Name
34+
// Needed to properly construct links to dive into a template
35+
const orgDictionary =
36+
organizations &&
37+
organizations.reduce((acc: Record<string, string>, curr: TypesGen.Organization) => {
38+
return {
39+
...acc,
40+
[curr.id]: curr.name,
41+
}
42+
}, {})
43+
44+
return (
45+
<Table>
46+
<TableHead>
47+
<TableTitle title={Language.tableTitle} />
48+
<TableHeaderRow>
49+
<TableCell size="small">{Language.nameLabel}</TableCell>
50+
</TableHeaderRow>
51+
</TableHead>
52+
<TableBody>
53+
{isLoading && <TableLoader />}
54+
{templates &&
55+
organizations &&
56+
orgDictionary &&
57+
templates.map((t) => (
58+
<TableRow key={t.id}>
59+
<TableCell>
60+
<Link to={`/templates/${orgDictionary[t.organization_id]}/${t.name}`}>{t.name}</Link>
61+
</TableCell>
62+
</TableRow>
63+
))}
64+
65+
{templates && templates.length === 0 && (
66+
<TableRow>
67+
<TableCell colSpan={999}>
68+
<Box p={4}>
69+
<EmptyState
70+
message={Language.emptyMessage}
71+
description={Language.emptyDescription}
72+
cta={<CodeExample code="coder templates create" />}
73+
/>
74+
</Box>
75+
</TableCell>
76+
</TableRow>
77+
)}
78+
</TableBody>
79+
</Table>
80+
)
81+
}

site/src/components/UsersTable/UsersTable.tsx

+40-34
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import * as TypesGen from "../../api/typesGenerated"
99
import { EmptyState } from "../EmptyState/EmptyState"
1010
import { RoleSelect } from "../RoleSelect/RoleSelect"
1111
import { TableHeaderRow } from "../TableHeaders/TableHeaders"
12+
import { TableLoader } from "../TableLoader/TableLoader"
1213
import { TableRowMenu } from "../TableRowMenu/TableRowMenu"
1314
import { TableTitle } from "../TableTitle/TableTitle"
1415
import { UserCell } from "../UserCell/UserCell"
@@ -24,12 +25,12 @@ export const Language = {
2425
}
2526

2627
export interface UsersTableProps {
27-
users: TypesGen.User[]
28+
users?: TypesGen.User[]
29+
roles?: TypesGen.Role[]
30+
isUpdatingUserRoles?: boolean
2831
onSuspendUser: (user: TypesGen.User) => void
2932
onResetUserPassword: (user: TypesGen.User) => void
3033
onUpdateUserRoles: (user: TypesGen.User, roles: TypesGen.Role["name"][]) => void
31-
roles: TypesGen.Role[]
32-
isUpdatingUserRoles?: boolean
3334
}
3435

3536
export const UsersTable: React.FC<UsersTableProps> = ({
@@ -40,6 +41,8 @@ export const UsersTable: React.FC<UsersTableProps> = ({
4041
onUpdateUserRoles,
4142
isUpdatingUserRoles,
4243
}) => {
44+
const isLoading = !users || !roles
45+
4346
return (
4447
<Table>
4548
<TableHead>
@@ -52,38 +55,41 @@ export const UsersTable: React.FC<UsersTableProps> = ({
5255
</TableHeaderRow>
5356
</TableHead>
5457
<TableBody>
55-
{users.map((u) => (
56-
<TableRow key={u.id}>
57-
<TableCell>
58-
<UserCell Avatar={{ username: u.username }} primaryText={u.username} caption={u.email} />{" "}
59-
</TableCell>
60-
<TableCell>
61-
<RoleSelect
62-
roles={roles}
63-
selectedRoles={u.roles}
64-
loading={isUpdatingUserRoles}
65-
onChange={(roles) => onUpdateUserRoles(u, roles)}
66-
/>
67-
</TableCell>
68-
<TableCell>
69-
<TableRowMenu
70-
data={u}
71-
menuItems={[
72-
{
73-
label: Language.suspendMenuItem,
74-
onClick: onSuspendUser,
75-
},
76-
{
77-
label: Language.resetPasswordMenuItem,
78-
onClick: onResetUserPassword,
79-
},
80-
]}
81-
/>
82-
</TableCell>
83-
</TableRow>
84-
))}
58+
{isLoading && <TableLoader />}
59+
{users &&
60+
roles &&
61+
users.map((u) => (
62+
<TableRow key={u.id}>
63+
<TableCell>
64+
<UserCell Avatar={{ username: u.username }} primaryText={u.username} caption={u.email} />{" "}
65+
</TableCell>
66+
<TableCell>
67+
<RoleSelect
68+
roles={roles}
69+
selectedRoles={u.roles}
70+
loading={isUpdatingUserRoles}
71+
onChange={(roles) => onUpdateUserRoles(u, roles)}
72+
/>
73+
</TableCell>
74+
<TableCell>
75+
<TableRowMenu
76+
data={u}
77+
menuItems={[
78+
{
79+
label: Language.suspendMenuItem,
80+
onClick: onSuspendUser,
81+
},
82+
{
83+
label: Language.resetPasswordMenuItem,
84+
onClick: onResetUserPassword,
85+
},
86+
]}
87+
/>
88+
</TableCell>
89+
</TableRow>
90+
))}
8591

86-
{users.length === 0 && (
92+
{users && users.length === 0 && (
8793
<TableRow>
8894
<TableCell colSpan={999}>
8995
<Box p={4}>

0 commit comments

Comments
 (0)