Skip to content

Commit 2e875aa

Browse files
committed
Implement simple projects page
1 parent c7fb16e commit 2e875aa

File tree

4 files changed

+145
-1
lines changed

4 files changed

+145
-1
lines changed

site/api.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ export namespace Project {
6767
}
6868
}
6969

70+
// Must be kept in sync with backend Workspace struct
71+
export interface Workspace {
72+
id: string
73+
created_at: string
74+
updated_at: string
75+
owner_id: string
76+
project_id: string
77+
name: string
78+
}
79+
7080
export const login = async (email: string, password: string): Promise<LoginResponse> => {
7181
const response = await fetch("/api/v2/login", {
7282
method: "POST",
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import React from "react"
2+
import Box from "@material-ui/core/Box"
3+
import { makeStyles } from "@material-ui/core/styles"
4+
import Paper from "@material-ui/core/Paper"
5+
import Link from "next/link"
6+
import { useRouter } from "next/router"
7+
import useSWR from "swr"
8+
9+
import { Project, Workspace } from "../../../api"
10+
import { Header } from "../../../components/Header"
11+
import { FullScreenLoader } from "../../../components/Loader/FullScreenLoader"
12+
import { Navbar } from "../../../components/Navbar"
13+
import { Footer } from "../../../components/Page"
14+
import { Column, Table } from "../../../components/Table"
15+
import { useUser } from "../../../contexts/UserContext"
16+
import { ErrorSummary } from "../../../components/ErrorSummary"
17+
import { firstOrItem } from "../../../util/array"
18+
import { EmptyState } from "../../../components/EmptyState"
19+
20+
import { MockWorkspace } from "../../../test_helpers"
21+
22+
const ProjectPage: React.FC = () => {
23+
const styles = useStyles()
24+
const { me, signOut } = useUser(true)
25+
26+
const router = useRouter()
27+
const { project, organization } = router.query
28+
29+
const { data: projectInfo, error: projectError } = useSWR<Project, Error>(
30+
() => `/api/v2/projects/${organization}/${project}`,
31+
)
32+
let { data: workspaces, error: workspacesError } = useSWR<Workspace[], Error>(
33+
() => `/api/v2/projects/${organization}/${project}/workspaces`,
34+
)
35+
36+
workspaces = [MockWorkspace]
37+
38+
if (projectError) {
39+
return <ErrorSummary error={projectError} />
40+
}
41+
42+
if (workspacesError) {
43+
return <ErrorSummary error={workspacesError} />
44+
}
45+
46+
if (!me || !projectInfo || !workspaces) {
47+
return <FullScreenLoader />
48+
}
49+
50+
const createWorkspace = () => {
51+
void router.push(`/projects/${organization}/${project}/create`)
52+
}
53+
54+
const emptyState = (
55+
<EmptyState
56+
button={{
57+
children: "Create Workspace",
58+
onClick: createWorkspace,
59+
}}
60+
message="No workspaces have been created yet"
61+
description="Create a workspace to get started"
62+
/>
63+
)
64+
65+
const columns: Column<Workspace>[] = [
66+
{
67+
key: "name",
68+
name: "Name",
69+
renderer: (nameField: string, data: Workspace) => {
70+
return <Link href={`/projects/${organization}/${project}/workspaces/${data.id}`}>{nameField}</Link>
71+
},
72+
},
73+
]
74+
75+
const tableProps = {
76+
title: "Workspaces",
77+
columns,
78+
data: workspaces,
79+
emptyState: emptyState,
80+
}
81+
82+
return (
83+
<div className={styles.root}>
84+
<Navbar user={me} onSignOut={signOut} />
85+
<Header
86+
title={firstOrItem(project)}
87+
description={firstOrItem(organization)}
88+
subTitle={`${workspaces.length} workspaces`}
89+
action={{
90+
text: "Create Workspace",
91+
onClick: createWorkspace,
92+
}}
93+
/>
94+
95+
<Paper style={{ maxWidth: "1380px", margin: "1em auto", width: "100%" }}>
96+
<Table {...tableProps} />
97+
</Paper>
98+
<Footer />
99+
</div>
100+
)
101+
}
102+
103+
const useStyles = makeStyles((theme) => ({
104+
root: {
105+
display: "flex",
106+
flexDirection: "column",
107+
},
108+
header: {
109+
display: "flex",
110+
flexDirection: "row-reverse",
111+
justifyContent: "space-between",
112+
margin: "1em auto",
113+
maxWidth: "1380px",
114+
padding: theme.spacing(2, 6.25, 0),
115+
width: "100%",
116+
},
117+
}))
118+
119+
export default ProjectPage

site/test_helpers/mocks.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { User } from "../contexts/UserContext"
2-
import { Provisioner, Organization, Project } from "../api"
2+
import { Provisioner, Organization, Project, Workspace } from "../api"
33

44
export const MockUser: User = {
55
id: "test-user-id",
@@ -29,3 +29,11 @@ export const MockOrganization: Organization = {
2929
created_at: "",
3030
updated_at: "",
3131
}
32+
33+
export const MockWorkspace: Workspace = {
34+
id: "test-workspace",
35+
name: "Test-Workspace",
36+
created_at: "",
37+
updated_at: "",
38+
project_id: "project-id",
39+
}

site/util/array.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const firstOrItem = <T>(itemOrItems: T | T[]): T | null => {
2+
if (Array.isArray(itemOrItems)) {
3+
return itemOrItems.length > 0 ? itemOrItems[0] : null
4+
}
5+
6+
return itemOrItems
7+
}

0 commit comments

Comments
 (0)