Skip to content

Commit

Permalink
feat: Add Create Workspace Form (#73)
Browse files Browse the repository at this point in the history
Fixes #38 

This adds a create-workspace form (with only 1 field, probably the simplest form ever 馃槃 )

![image](https://user-images.githubusercontent.com/88213859/151108220-8a540c75-e55b-49af-8199-c69394508700.png)

It currently redirects to a path `/workspaces/<unique id>`, but that isn't implemented yet - but you can see the workspace show up on the projects page.
  • Loading branch information
bryphe-coder committed Jan 31, 2022
1 parent b586a35 commit 7cf686c
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 0 deletions.
30 changes: 30 additions & 0 deletions site/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ export namespace Project {
}
}

export interface CreateWorkspaceRequest {
name: string
project_id: string
}

// Must be kept in sync with backend Workspace struct
export interface Workspace {
id: string
Expand All @@ -77,6 +82,31 @@ export interface Workspace {
name: string
}

export namespace Workspace {
export const create = async (request: CreateWorkspaceRequest): Promise<Workspace> => {
const response = await fetch(`/api/v2/workspaces/me`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(request),
})

const body = await response.json()
if (!response.ok) {
throw new Error(body.message)
}

// Let SWR know that both the /api/v2/workspaces/* and /api/v2/projects/*
// endpoints will need to fetch new data.
const mutateWorkspacesPromise = mutate("/api/v2/workspaces")
const mutateProjectsPromise = mutate("/api/v2/projects")
await Promise.all([mutateWorkspacesPromise, mutateProjectsPromise])

return body
}
}

export const login = async (email: string, password: string): Promise<LoginResponse> => {
const response = await fetch("/api/v2/login", {
method: "POST",
Expand Down
20 changes: 20 additions & 0 deletions site/forms/CreateWorkspaceForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { render, screen } from "@testing-library/react"
import React from "react"
import { CreateWorkspaceForm } from "./CreateWorkspaceForm"
import { MockProject, MockWorkspace } from "./../test_helpers"

describe("CreateWorkspaceForm", () => {
it("renders", async () => {
// Given
const onSubmit = () => Promise.resolve(MockWorkspace)
const onCancel = () => Promise.resolve()

// When
render(<CreateWorkspaceForm project={MockProject} onSubmit={onSubmit} onCancel={onCancel} />)

// Then
// Simple smoke test to verify form renders
const element = await screen.findByText("Create Workspace")
expect(element).toBeDefined()
})
})
97 changes: 97 additions & 0 deletions site/forms/CreateWorkspaceForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import Button from "@material-ui/core/Button"
import { makeStyles } from "@material-ui/core/styles"
import { FormikContextType, useFormik } from "formik"
import React from "react"
import * as Yup from "yup"

import { FormTextField, FormTitle, FormSection } from "../components/Form"
import { LoadingButton } from "../components/Button"
import { Project, Workspace, CreateWorkspaceRequest } from "../api"

export interface CreateWorkspaceForm {
project: Project
onSubmit: (request: CreateWorkspaceRequest) => Promise<Workspace>
onCancel: () => void
}

const validationSchema = Yup.object({
name: Yup.string().required("Name is required"),
})

export const CreateWorkspaceForm: React.FC<CreateWorkspaceForm> = ({ project, onSubmit, onCancel }) => {
const styles = useStyles()

const form: FormikContextType<{ name: string }> = useFormik<{ name: string }>({
initialValues: {
name: "",
},
enableReinitialize: true,
validationSchema: validationSchema,
onSubmit: ({ name }) => {
return onSubmit({
project_id: project.id,
name: name,
})
},
})

return (
<div className={styles.root}>
<FormTitle
title="Create Workspace"
detail={
<span>
for project <strong>{project.name}</strong>
</span>
}
/>
<FormSection title="Name">
<FormTextField
form={form}
formFieldName="name"
fullWidth
helperText="A unique name describing your workspace."
label="Workspace Name"
placeholder="my-workspace"
required
/>
</FormSection>

<div className={styles.footer}>
<Button className={styles.button} onClick={onCancel} variant="outlined">
Cancel
</Button>
<LoadingButton
loading={form.isSubmitting}
className={styles.button}
onClick={form.submitForm}
variant="contained"
color="primary"
type="submit"
>
Submit
</LoadingButton>
</div>
</div>
)
}

const useStyles = makeStyles(() => ({
root: {
maxWidth: "1380px",
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
},
footer: {
display: "flex",
flex: "0",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
},
button: {
margin: "1em",
},
}))
56 changes: 56 additions & 0 deletions site/pages/projects/[organization]/[project]/create.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from "react"
import { makeStyles } from "@material-ui/core/styles"
import { useRouter } from "next/router"
import useSWR from "swr"

import * as API from "../../../../api"
import { useUser } from "../../../../contexts/UserContext"
import { ErrorSummary } from "../../../../components/ErrorSummary"
import { FullScreenLoader } from "../../../../components/Loader/FullScreenLoader"
import { CreateWorkspaceForm } from "../../../../forms/CreateWorkspaceForm"

const CreateWorkspacePage: React.FC = () => {
const router = useRouter()
const styles = useStyles()
const { me } = useUser(/* redirectOnError */ true)
const { organization, project: projectName } = router.query
const { data: project, error: projectError } = useSWR<API.Project, Error>(
`/api/v2/projects/${organization}/${projectName}`,
)

if (projectError) {
return <ErrorSummary error={projectError} />
}

if (!me || !project) {
return <FullScreenLoader />
}

const onCancel = async () => {
await router.push(`/projects/${organization}/${project}`)
}

const onSubmit = async (req: API.CreateWorkspaceRequest) => {
const workspace = await API.Workspace.create(req)
await router.push(`/workspaces/${workspace.id}`)
return workspace
}

return (
<div className={styles.root}>
<CreateWorkspaceForm onCancel={onCancel} onSubmit={onSubmit} project={project} />
</div>
)
}

const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
flexDirection: "column",
alignItems: "center",
height: "100vh",
backgroundColor: theme.palette.background.paper,
},
}))

export default CreateWorkspacePage

0 comments on commit 7cf686c

Please sign in to comment.