Skip to content

Commit ea40bed

Browse files
authored
Merge branch 'main' into buffered
2 parents 60cb674 + 4954a6b commit ea40bed

File tree

5 files changed

+191
-0
lines changed

5 files changed

+191
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Story } from "@storybook/react"
2+
import React from "react"
3+
import { Workspace, WorkspaceProps } from "./Workspace"
4+
import { MockWorkspace } from "../../test_helpers"
5+
6+
export default {
7+
title: "Workspace",
8+
component: Workspace,
9+
argTypes: {},
10+
}
11+
12+
const Template: Story<WorkspaceProps> = (args) => <Workspace {...args} />
13+
14+
export const Example = Template.bind({})
15+
Example.args = {
16+
workspace: MockWorkspace,
17+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { render, screen } from "@testing-library/react"
2+
import React from "react"
3+
import { Workspace } from "./Workspace"
4+
import { MockWorkspace } from "../../test_helpers"
5+
6+
describe("Workspace", () => {
7+
it("renders", async () => {
8+
// When
9+
render(<Workspace workspace={MockWorkspace} />)
10+
11+
// Then
12+
const element = await screen.findByText(MockWorkspace.name)
13+
expect(element).toBeDefined()
14+
})
15+
})
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import Box from "@material-ui/core/Box"
2+
import Paper from "@material-ui/core/Paper"
3+
import Typography from "@material-ui/core/Typography"
4+
import { makeStyles } from "@material-ui/core/styles"
5+
import CloudCircleIcon from "@material-ui/icons/CloudCircle"
6+
import Link from "next/link"
7+
import React from "react"
8+
9+
import * as API from "../../api"
10+
11+
export interface WorkspaceProps {
12+
workspace: API.Workspace
13+
}
14+
15+
namespace Constants {
16+
export const TitleIconSize = 48
17+
export const CardRadius = 8
18+
export const CardPadding = 20
19+
}
20+
21+
/**
22+
* Workspace is the top-level component for viewing an individual workspace
23+
*/
24+
export const Workspace: React.FC<WorkspaceProps> = ({ workspace }) => {
25+
const styles = useStyles()
26+
27+
return (
28+
<div className={styles.root}>
29+
<WorkspaceHeader workspace={workspace} />
30+
</div>
31+
)
32+
}
33+
34+
/**
35+
* Component for the header at the top of the workspace page
36+
*/
37+
export const WorkspaceHeader: React.FC<WorkspaceProps> = ({ workspace }) => {
38+
const styles = useStyles()
39+
40+
return (
41+
<Paper elevation={0} className={styles.section}>
42+
<div className={styles.horizontal}>
43+
<WorkspaceHeroIcon />
44+
<div className={styles.vertical}>
45+
<Typography variant="h4">{workspace.name}</Typography>
46+
<Typography variant="body2" color="textSecondary">
47+
<Link href="javascript:;">{workspace.project_id}</Link>
48+
</Typography>
49+
</div>
50+
</div>
51+
</Paper>
52+
)
53+
}
54+
55+
/**
56+
* Component to render the 'Hero Icon' in the header of a workspace
57+
*/
58+
export const WorkspaceHeroIcon: React.FC = () => {
59+
return (
60+
<Box mr="1em">
61+
<CloudCircleIcon width={Constants.TitleIconSize} height={Constants.TitleIconSize} />
62+
</Box>
63+
)
64+
}
65+
66+
export const useStyles = makeStyles((theme) => {
67+
return {
68+
root: {
69+
display: "flex",
70+
flexDirection: "column",
71+
},
72+
horizontal: {
73+
display: "flex",
74+
flexDirection: "row",
75+
},
76+
vertical: {
77+
display: "flex",
78+
flexDirection: "column",
79+
},
80+
section: {
81+
border: `1px solid ${theme.palette.divider}`,
82+
borderRadius: Constants.CardRadius,
83+
padding: Constants.CardPadding,
84+
},
85+
icon: {
86+
width: Constants.TitleIconSize,
87+
height: Constants.TitleIconSize,
88+
},
89+
}
90+
})

site/components/Workspace/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./Workspace"
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import React from "react"
2+
import useSWR from "swr"
3+
import { makeStyles } from "@material-ui/core/styles"
4+
import { useRouter } from "next/router"
5+
import { Navbar } from "../../../components/Navbar"
6+
import { Footer } from "../../../components/Page"
7+
import { useUser } from "../../../contexts/UserContext"
8+
import { firstOrItem } from "../../../util/array"
9+
import { ErrorSummary } from "../../../components/ErrorSummary"
10+
import { FullScreenLoader } from "../../../components/Loader/FullScreenLoader"
11+
import { Workspace } from "../../../components/Workspace"
12+
13+
import * as API from "../../../api"
14+
15+
const WorkspacesPage: React.FC = () => {
16+
const styles = useStyles()
17+
const router = useRouter()
18+
const { me, signOut } = useUser(true)
19+
20+
const { user: userQueryParam, workspace: workspaceQueryParam } = router.query
21+
22+
const { data: workspace, error: workspaceError } = useSWR<API.Workspace, Error>(() => {
23+
const userParam = firstOrItem(userQueryParam, null)
24+
const workspaceParam = firstOrItem(workspaceQueryParam, null)
25+
26+
// TODO(Bryan): Getting non-personal users isn't supported yet in the backend.
27+
// So if the user is the same as 'me', use 'me' as the parameter
28+
const normalizedUserParam = me && userParam === me.id ? "me" : userParam
29+
30+
// The SWR API expects us to 'throw' if the query isn't ready yet, so these casts to `any` are OK
31+
// because the API expects exceptions.
32+
return `/api/v2/workspaces/${(normalizedUserParam as any).toString()}/${(workspaceParam as any).toString()}`
33+
})
34+
35+
if (workspaceError) {
36+
return <ErrorSummary error={workspaceError} />
37+
}
38+
39+
if (!me || !workspace) {
40+
return <FullScreenLoader />
41+
}
42+
43+
return (
44+
<div className={styles.root}>
45+
<Navbar user={me} onSignOut={signOut} />
46+
47+
<div className={styles.inner}>
48+
<Workspace workspace={workspace} />
49+
</div>
50+
51+
<Footer />
52+
</div>
53+
)
54+
}
55+
56+
const useStyles = makeStyles(() => ({
57+
root: {
58+
display: "flex",
59+
flexDirection: "column",
60+
},
61+
inner: {
62+
maxWidth: "1380px",
63+
margin: "1em auto",
64+
width: "100%",
65+
},
66+
}))
67+
68+
export default WorkspacesPage

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