Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: Initial Project Create Form ('/projects/create') #60

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

Merged
merged 16 commits into from
Jan 26, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
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
54 changes: 54 additions & 0 deletions site/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,35 @@
import { mutate } from "swr"

interface LoginResponse {
session_token: string
}

/**
* `Organization` must be kept in sync with the go struct in organizations.go
*/
export interface Organization {
id: string
name: string
created_at: string
updated_at: string
}

export interface Provisioner {
id: string
name: string
}

export const provisioners: Provisioner[] = [
{
id: "terraform",
name: "Terraform",
},
{
id: "cdr-basic",
name: "Basic",
},
]

// This must be kept in sync with the `Project` struct in the back-end
export interface Project {
id: string
Expand All @@ -13,6 +41,32 @@ export interface Project {
active_version_id: string
}

export interface CreateProjectRequest {
name: string
organizationId: string
provisioner: string
}

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

const body = await response.json()
await mutate("/api/v2/projects")
if (!response.ok) {
throw new Error(body.message)
}

return body
}
}

export const login = async (email: string, password: string): Promise<LoginResponse> => {
const response = await fetch("/api/v2/login", {
method: "POST",
Expand Down
55 changes: 55 additions & 0 deletions site/components/Form/FormDropdownField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Box from "@material-ui/core/Box"
import MenuItem from "@material-ui/core/MenuItem"
import { makeStyles } from "@material-ui/core/styles"
import Typography from "@material-ui/core/Typography"
import React from "react"

import { formTextFieldFactory, FormTextFieldProps } from "./FormTextField"

export interface DropdownItem {
value: string
name: string
description?: string
}

export interface FormDropdownFieldProps<T> extends FormTextFieldProps<T> {
items: DropdownItem[]
}

export const formDropdownFieldFactory = <T,>(): React.FC<FormDropdownFieldProps<T>> => {
const FormTextField = formTextFieldFactory<T>()

const Component: React.FC<FormDropdownFieldProps<T>> = ({ items, ...props }) => {
const styles = useStyles()
return (
<FormTextField select {...props}>
{items.map((item: DropdownItem) => (
<MenuItem key={item.value} value={item.value}>
<Box alignItems="center" display="flex">
<Box ml={1}>
<Typography>{item.name}</Typography>
</Box>
{item.description && (
<Box ml={1}>
<Typography className={styles.hintText} variant="caption">
{item.description}
</Typography>
</Box>
)}
</Box>
</MenuItem>
))}
</FormTextField>
)
}

// Required when using an anonymous factory function
Component.displayName = "FormDropdownField"
return Component
}

const useStyles = makeStyles({
hintText: {
opacity: 0.75,
},
})
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(4),
marginBottom: theme.spacing(4),
},
}))

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>
)
}
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 FormTitleProps {
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 FormTitle: React.FC<FormTitleProps> = ({ title, detail }) => {
const styles = useStyles()

return (
<div className={styles.title}>
<Typography variant="h3">{title}</Typography>
{detail && <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 "./FormSection"
export * from "./FormDropdownField"
export * from "./FormTextField"
export * from "./FormTitle"
1 change: 0 additions & 1 deletion site/components/Form/index.tsx

This file was deleted.

29 changes: 29 additions & 0 deletions site/forms/CreateProjectForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { render, screen } from "@testing-library/react"
import React from "react"
import { CreateProjectForm } from "./CreateProjectForm"
import { MockProvisioner, MockOrganization, MockProject } from "./../test_helpers"

describe("CreateProjectForm", () => {
it("renders", async () => {
// Given
const provisioners = [MockProvisioner]
const organizations = [MockOrganization]
const onSubmit = () => Promise.resolve(MockProject)
const onCancel = () => Promise.resolve()

// When
render(
<CreateProjectForm
provisioners={provisioners}
organizations={organizations}
onSubmit={onSubmit}
onCancel={onCancel}
/>,
)

// Then
// Simple smoke test to verify form renders
const element = await screen.findByText("Create Project")
expect(element).toBeDefined()
})
})
Loading