Skip to content

feat: UX - Initial create form flow #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 45 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
18dd18f
Add several dependencies needed for API routes
bryphe-coder Jan 19, 2022
7329cca
Add placeholder API and projectService
bryphe-coder Jan 19, 2022
6726f40
Create initial workspaces API
bryphe-coder Jan 19, 2022
3f932c2
Port over SafeHydrate component
bryphe-coder Jan 19, 2022
6fdb43f
Stub pages for workspace creation
bryphe-coder Jan 19, 2022
97b2383
Add missed favicons
bryphe-coder Jan 19, 2022
086d533
Formatting
bryphe-coder Jan 19, 2022
82989d3
Merge branch 'bryphe/fix/css-hydration-errors' into bryphe/feat/initi…
bryphe-coder Jan 19, 2022
c305c8d
Initial select page
bryphe-coder Jan 19, 2022
cc411d0
Flip to light theme
bryphe-coder Jan 19, 2022
907a523
Start pulling out AppPage/FormPage
bryphe-coder Jan 19, 2022
56f7515
Start stubbing out API
bryphe-coder Jan 19, 2022
0a5c48a
Start scaffolding project selection
bryphe-coder Jan 19, 2022
aea58ae
Add placeholder icon if none available
bryphe-coder Jan 19, 2022
c92e454
Revert prototyping changes
bryphe-coder Jan 19, 2022
be4c90f
Revert sum changes
bryphe-coder Jan 19, 2022
308de2b
Initial create form
bryphe-coder Jan 19, 2022
d0b0ef1
Rename project function
bryphe-coder Jan 19, 2022
797c82b
Add formik
bryphe-coder Jan 19, 2022
5016b9a
Stub create workspace API
bryphe-coder Jan 19, 2022
8da382b
Hook up formik to create form
bryphe-coder Jan 20, 2022
6fc0a54
Initial shared loading logic
bryphe-coder Jan 20, 2022
e57d1b6
Set up dynamic form components
bryphe-coder Jan 20, 2022
08010c3
Remove leftover buildmode pkg
bryphe-coder Jan 20, 2022
8800445
Add note in api that this is temporary
bryphe-coder Jan 20, 2022
496b42e
Factor ProjectName to separate file
bryphe-coder Jan 20, 2022
646206e
Fix useStyles naming
bryphe-coder Jan 20, 2022
4b6f1c5
Remove accidentally duplicated SafeHydrate
bryphe-coder Jan 20, 2022
961a44b
Remove addition of srverr package
bryphe-coder Jan 20, 2022
eb46f4c
Fix usage of render prop pattern
bryphe-coder Jan 20, 2022
840a796
Fix some compilation issues
bryphe-coder Jan 20, 2022
f20171f
Clean up imports
bryphe-coder Jan 20, 2022
03236df
Add some initial form tests
bryphe-coder Jan 20, 2022
61cd37f
Add smoke test for ProjectIcon
bryphe-coder Jan 20, 2022
99d0a95
Add AppPage smoke test
bryphe-coder Jan 20, 2022
d52bd38
Fix up formatting
bryphe-coder Jan 20, 2022
76d11d4
Remove api from collection metrics since it is temporary
bryphe-coder Jan 20, 2022
ffac475
Clean up unused import
bryphe-coder Jan 20, 2022
e95d75f
Remove and consolidate form ty pes
bryphe-coder Jan 22, 2022
3e8069b
Formatting
bryphe-coder Jan 22, 2022
9492e8e
Merge master
bryphe-coder Jan 22, 2022
e5e02f9
Clean-up utilities
bryphe-coder Jan 22, 2022
5e4a44f
Revert "Remove and consolidate form ty pes"
bryphe-coder Jan 22, 2022
6fe7141
Merge branch 'main' into bryphe/feat/initial-create-form
bryphe-coder Jan 22, 2022
8343ef0
First round of lint failures
bryphe-coder Jan 22, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
"@material-ui/core": "4.9.4",
"@material-ui/icons": "4.5.1",
"@material-ui/lab": "4.0.0-alpha.42",
"@testing-library/jest-dom": "5.16.1",
"@testing-library/react": "12.1.2",
"@testing-library/user-event": "13.5.0",
"@types/express": "4.17.13",
"@types/jest": "27.4.0",
"@types/node": "14.18.4",
Expand Down
107 changes: 106 additions & 1 deletion site/api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,108 @@
import { wait } from "./util"

// TEMPORARY
// This is all placeholder / stub code until we have a real API to work with!
//
// The implementations below that are hard-coded will switch to using `fetch`
// once the routes are available.
// TEMPORARY

export type ProjectParameterType = "string" | "number"

export interface ProjectParameter {
id: string
name: string
description: string
defaultValue?: string
type: ProjectParameterType
}

export interface Project {
id: string
icon?: string
name: string
description: string
parameters: ProjectParameter[]
}

export namespace Project {
const testProject1: Project = {
id: "test-terraform-1",
icon: "https://www.datocms-assets.com/2885/1620155117-brandhcterraformverticalcolorwhite.svg",
name: "Terraform Project 1",
description: "Simple terraform project that deploys a kubernetes provider",
parameters: [
{
id: "namespace",
name: "Namespace",
description: "The kubernetes namespace that will own the workspace pod.",
defaultValue: "default",
type: "string",
},
{
id: "cpu_cores",
name: "CPU Cores",
description: "Number of CPU cores to allocate for pod.",
type: "number",
},
],
}

const testProject2: Project = {
id: "test-echo-1",
name: "Echo Project",
description: "Project built on echo provisioner",
parameters: [
{
id: "echo_string",
name: "Echo string",
description: "String that will be echoed out during build.",
type: "string",
},
],
}

const allProjects = [testProject1, testProject2]

export const getAllProjectsInOrg = async (_org: string): Promise<Project[]> => {
await wait(250)
return allProjects
}

export const getProject = async (_org: string, projectId: string): Promise<Project> => {
await wait(250)

const matchingProjects = allProjects.filter((p) => p.id === projectId)

if (matchingProjects.length === 0) {
throw new Error(`No project matching ${projectId} found`)
}

return matchingProjects[0]
}

export const createWorkspace = async (name: string): Promise<string> => {
await wait(250)
return name
}
}

export namespace Workspace {
export type WorkspaceId = string

export const createWorkspace = (
name: string,
projectTemplate: string,
parameters: Record<string, string>,
): Promise<WorkspaceId> => {
alert(
`Creating workspace named ${name} for project ${projectTemplate} with parameters: ${JSON.stringify(parameters)}`,
)

return Promise.resolve("test-workspace")
}
}

interface LoginResponse {
session_token: string
}
Expand All @@ -20,4 +125,4 @@ export const login = async (email: string, password: string): Promise<LoginRespo
}

return body
}
}
14 changes: 14 additions & 0 deletions site/components/Form/FormRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { makeStyles } from "@material-ui/core/styles"
import React from "react"

const useStyles = makeStyles((theme) => ({
row: {
marginTop: theme.spacing(2),
marginBottom: theme.spacing(2),
},
}))

export const FormRow: React.FC = ({ children }) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we believe it's safe to say all of our Form instances will follow a similar format? I have lots of hesitancy over a top-level form component. If we even have a single form that doesn't consume it, this top-level component abstraction has broken.

I'm not sure of an alternative though, so maybe this is required!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a good point for this component - it's only used in one places so I can bring it inline.

const styles = useStyles()
return <div className={styles.row}>{children}</div>
}
60 changes: 60 additions & 0 deletions site/components/Form/FormSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { makeStyles } from "@material-ui/core/styles"
import Typography from "@material-ui/core/Typography"
import React from "react"

export interface FormSectionProps {
title: string
description?: string
}

export const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
flexDirection: "row",
// Borrowed from PaperForm styles
maxWidth: "852px",
width: "100%",
borderBottom: `1px solid ${theme.palette.divider}`,
},
descriptionContainer: {
maxWidth: "200px",
flex: "0 0 200px",
display: "flex",
flexDirection: "column",
justifyContent: "flex-start",
alignItems: "flex-start",
marginTop: theme.spacing(5),
marginBottom: theme.spacing(2),
},
descriptionText: {
fontSize: "0.9em",
lineHeight: "1em",
color: theme.palette.text.secondary,
marginTop: theme.spacing(1),
},
contents: {
flex: 1,
marginTop: theme.spacing(2),
marginBottom: theme.spacing(2),
},
}))

export const FormSection: React.FC<FormSectionProps> = ({ title, description, children }) => {
const styles = useStyles()

return (
<div className={styles.root}>
<div className={styles.descriptionContainer}>
<Typography variant="h5" color="textPrimary">
{title}
</Typography>
{description && (
<Typography className={styles.descriptionText} variant="body2" color="textSecondary">
{description}
</Typography>
)}
</div>
<div className={styles.contents}>{children}</div>
</div>
)
}
58 changes: 29 additions & 29 deletions site/components/Form/FormTextField.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import TextField, { TextFieldProps } from "@material-ui/core/TextField"
import { FormikLike } from "../../util/formik"
import React from "react"
import { PasswordField } from "./PasswordField"
import { FormikContextType } from "formik"

/**
* FormFieldProps are required props for creating form fields using a factory.
Expand All @@ -11,7 +11,7 @@ export interface FormFieldProps<T> {
* form is a reference to a form or subform and is used to compute common
* states such as error and helper text
*/
form: FormikContextType<T>
form: FormikLike<T>
/**
* formFieldName is a field name associated with the form schema.
*/
Expand All @@ -26,31 +26,31 @@ export interface FormFieldProps<T> {
*/
export interface FormTextFieldProps<T>
extends Pick<
TextFieldProps,
| "autoComplete"
| "autoFocus"
| "children"
| "className"
| "disabled"
| "fullWidth"
| "helperText"
| "id"
| "InputLabelProps"
| "InputProps"
| "inputProps"
| "label"
| "margin"
| "multiline"
| "onChange"
| "placeholder"
| "required"
| "rows"
| "select"
| "SelectProps"
| "style"
| "type"
>,
FormFieldProps<T> {
TextFieldProps,
| "autoComplete"
| "autoFocus"
| "children"
| "className"
| "disabled"
| "fullWidth"
| "helperText"
| "id"
| "InputLabelProps"
| "InputProps"
| "inputProps"
| "label"
| "margin"
| "multiline"
| "onChange"
| "placeholder"
| "required"
| "rows"
| "select"
| "SelectProps"
| "style"
| "type"
>,
FormFieldProps<T> {
/**
* eventTransform is an optional transformer on the event data before it is
* processed by formik.
Expand Down Expand Up @@ -124,7 +124,7 @@ export const formTextFieldFactory = <T,>(): React.FC<FormTextFieldProps<T>> => {

// Conversion to a string primitive is necessary as formFieldName is an in
// indexable type such as a string, number or enum.
const fieldId = String(formFieldName)
const fieldId = FormikLike.getFieldId<T>(form, String(formFieldName))

const Component = isPassword ? PasswordField : TextField
const inputType = isPassword ? undefined : type
Expand Down Expand Up @@ -167,4 +167,4 @@ export const formTextFieldFactory = <T,>(): React.FC<FormTextFieldProps<T>> => {
// Required when using an anonymous factory function
component.displayName = "FormTextField"
return component
}
}
31 changes: 31 additions & 0 deletions site/components/Form/FormTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { makeStyles } from "@material-ui/core/styles"
import Typography from "@material-ui/core/Typography"
import React from "react"

export interface TitleProps {
title: string
detail: React.ReactNode
}

const useStyles = makeStyles((theme) => ({
title: {
textAlign: "center",
marginTop: theme.spacing(5),
marginBottom: theme.spacing(5),

"& h3": {
marginBottom: theme.spacing(1),
},
},
}))

export const Title: React.FC<TitleProps> = ({ title, detail }) => {
const styles = useStyles()

return (
<div className={styles.title}>
<Typography variant="h3">{title}</Typography>
<Typography variant="caption">{detail}</Typography>
</div>
)
}
4 changes: 4 additions & 0 deletions site/components/Form/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./FormRow"
export * from "./FormSection"
export * from "./FormTextField"
export * from "./FormTitle"
16 changes: 16 additions & 0 deletions site/components/Form/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { FormikLike } from "../../util/formik"

/**
* FormFieldProps are required props for creating form fields using a factory.
*/
export interface FormFieldProps<T> {
/**
* form is a reference to a form or subform and is used to compute common
* states such as error and helper text
*/
form: FormikLike<T>
/**
* formFieldName is a field name associated with the form schema.
*/
formFieldName: keyof T
}
12 changes: 2 additions & 10 deletions site/components/Navbar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import React from "react"
import Button from "@material-ui/core/Button"
import List from "@material-ui/core/List"
import ListSubheader from "@material-ui/core/ListSubheader"
import { makeStyles } from "@material-ui/core/styles"
import Link from "next/link"

Expand All @@ -23,14 +21,8 @@ export const Navbar: React.FC<NavbarProps> = () => {
</Button>
</Link>
</div>
<div className={styles.fullWidth}>
<div className={styles.title}>Coder v2</div>
</div>
<div className={styles.fixed}>
<List>
<ListSubheader>Manage</ListSubheader>
</List>
</div>
<div className={styles.fullWidth} />
<div className={styles.fixed} />
</div>
)
}
Expand Down
15 changes: 0 additions & 15 deletions site/components/Page/Footer.test.tsx

This file was deleted.

1 change: 0 additions & 1 deletion site/components/Page/index.tsx

This file was deleted.

Loading