diff --git a/site/components/Workspace/Workspace.stories.tsx b/site/components/Workspace/Workspace.stories.tsx new file mode 100644 index 0000000000000..4e03c3b456e4e --- /dev/null +++ b/site/components/Workspace/Workspace.stories.tsx @@ -0,0 +1,17 @@ +import { Story } from "@storybook/react" +import React from "react" +import { Workspace, WorkspaceProps } from "./Workspace" +import { MockWorkspace } from "../../test_helpers" + +export default { + title: "Workspace", + component: Workspace, + argTypes: {}, +} + +const Template: Story = (args) => + +export const Example = Template.bind({}) +Example.args = { + workspace: MockWorkspace, +} diff --git a/site/components/Workspace/Workspace.test.tsx b/site/components/Workspace/Workspace.test.tsx new file mode 100644 index 0000000000000..245455e729da1 --- /dev/null +++ b/site/components/Workspace/Workspace.test.tsx @@ -0,0 +1,15 @@ +import { render, screen } from "@testing-library/react" +import React from "react" +import { Workspace } from "./Workspace" +import { MockWorkspace } from "../../test_helpers" + +describe("Workspace", () => { + it("renders", async () => { + // When + render() + + // Then + const element = await screen.findByText(MockWorkspace.name) + expect(element).toBeDefined() + }) +}) diff --git a/site/components/Workspace/Workspace.tsx b/site/components/Workspace/Workspace.tsx new file mode 100644 index 0000000000000..23d9cf07d32d9 --- /dev/null +++ b/site/components/Workspace/Workspace.tsx @@ -0,0 +1,90 @@ +import Box from "@material-ui/core/Box" +import Paper from "@material-ui/core/Paper" +import Typography from "@material-ui/core/Typography" +import { makeStyles } from "@material-ui/core/styles" +import CloudCircleIcon from "@material-ui/icons/CloudCircle" +import Link from "next/link" +import React from "react" + +import * as API from "../../api" + +export interface WorkspaceProps { + workspace: API.Workspace +} + +namespace Constants { + export const TitleIconSize = 48 + export const CardRadius = 8 + export const CardPadding = 20 +} + +/** + * Workspace is the top-level component for viewing an individual workspace + */ +export const Workspace: React.FC = ({ workspace }) => { + const styles = useStyles() + + return ( +
+ +
+ ) +} + +/** + * Component for the header at the top of the workspace page + */ +export const WorkspaceHeader: React.FC = ({ workspace }) => { + const styles = useStyles() + + return ( + +
+ +
+ {workspace.name} + + {workspace.project_id} + +
+
+
+ ) +} + +/** + * Component to render the 'Hero Icon' in the header of a workspace + */ +export const WorkspaceHeroIcon: React.FC = () => { + return ( + + + + ) +} + +export const useStyles = makeStyles((theme) => { + return { + root: { + display: "flex", + flexDirection: "column", + }, + horizontal: { + display: "flex", + flexDirection: "row", + }, + vertical: { + display: "flex", + flexDirection: "column", + }, + section: { + border: `1px solid ${theme.palette.divider}`, + borderRadius: Constants.CardRadius, + padding: Constants.CardPadding, + }, + icon: { + width: Constants.TitleIconSize, + height: Constants.TitleIconSize, + }, + } +}) diff --git a/site/components/Workspace/index.ts b/site/components/Workspace/index.ts new file mode 100644 index 0000000000000..4c8c38cc721c8 --- /dev/null +++ b/site/components/Workspace/index.ts @@ -0,0 +1 @@ +export * from "./Workspace" diff --git a/site/pages/workspaces/[user]/[workspace].tsx b/site/pages/workspaces/[user]/[workspace].tsx new file mode 100644 index 0000000000000..6ae0c52713697 --- /dev/null +++ b/site/pages/workspaces/[user]/[workspace].tsx @@ -0,0 +1,68 @@ +import React from "react" +import useSWR from "swr" +import { makeStyles } from "@material-ui/core/styles" +import { useRouter } from "next/router" +import { Navbar } from "../../../components/Navbar" +import { Footer } from "../../../components/Page" +import { useUser } from "../../../contexts/UserContext" +import { firstOrItem } from "../../../util/array" +import { ErrorSummary } from "../../../components/ErrorSummary" +import { FullScreenLoader } from "../../../components/Loader/FullScreenLoader" +import { Workspace } from "../../../components/Workspace" + +import * as API from "../../../api" + +const WorkspacesPage: React.FC = () => { + const styles = useStyles() + const router = useRouter() + const { me, signOut } = useUser(true) + + const { user: userQueryParam, workspace: workspaceQueryParam } = router.query + + const { data: workspace, error: workspaceError } = useSWR(() => { + const userParam = firstOrItem(userQueryParam, null) + const workspaceParam = firstOrItem(workspaceQueryParam, null) + + // TODO(Bryan): Getting non-personal users isn't supported yet in the backend. + // So if the user is the same as 'me', use 'me' as the parameter + const normalizedUserParam = me && userParam === me.id ? "me" : userParam + + // The SWR API expects us to 'throw' if the query isn't ready yet, so these casts to `any` are OK + // because the API expects exceptions. + return `/api/v2/workspaces/${(normalizedUserParam as any).toString()}/${(workspaceParam as any).toString()}` + }) + + if (workspaceError) { + return + } + + if (!me || !workspace) { + return + } + + return ( +
+ + +
+ +
+ +
+ ) +} + +const useStyles = makeStyles(() => ({ + root: { + display: "flex", + flexDirection: "column", + }, + inner: { + maxWidth: "1380px", + margin: "1em auto", + width: "100%", + }, +})) + +export default WorkspacesPage