Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitb7234a6

Browse files
authored
fix: push create workspace UX to templates page (#2142)
1 parent119db78 commitb7234a6

File tree

10 files changed

+79
-210
lines changed

10 files changed

+79
-210
lines changed

‎site/src/AppRouter.tsx

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,6 @@ export const AppRouter: FC = () => (
5656
</AuthAndFrame>
5757
}
5858
/>
59-
60-
<Route
61-
path="new"
62-
element={
63-
<RequireAuth>
64-
<CreateWorkspacePage/>
65-
</RequireAuth>
66-
}
67-
/>
6859
</Route>
6960

7061
<Routepath="templates">
@@ -77,14 +68,24 @@ export const AppRouter: FC = () => (
7768
}
7869
/>
7970

80-
<Route
81-
path=":template"
82-
element={
83-
<AuthAndFrame>
84-
<TemplatePage/>
85-
</AuthAndFrame>
86-
}
87-
/>
71+
<Routepath=":template">
72+
<Route
73+
index
74+
element={
75+
<AuthAndFrame>
76+
<TemplatePage/>
77+
</AuthAndFrame>
78+
}
79+
/>
80+
<Route
81+
path="workspace"
82+
element={
83+
<RequireAuth>
84+
<CreateWorkspacePage/>
85+
</RequireAuth>
86+
}
87+
/>
88+
</Route>
8889
</Route>
8990

9091
<Routepath="users">

‎site/src/components/PageHeader/PageHeader.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ export const PageHeaderSubtitle: React.FC = ({ children }) => {
3232
return<h2className={styles.subtitle}>{children}</h2>
3333
}
3434

35+
exportconstPageHeaderText:React.FC=({ children})=>{
36+
conststyles=useStyles()
37+
38+
return<h3className={styles.text}>{children}</h3>
39+
}
40+
3541
constuseStyles=makeStyles((theme)=>({
3642
root:{
3743
display:"flex",
@@ -58,6 +64,15 @@ const useStyles = makeStyles((theme) => ({
5864
marginTop:theme.spacing(1),
5965
},
6066

67+
text:{
68+
fontSize:theme.spacing(2),
69+
color:theme.palette.text.secondary,
70+
fontWeight:400,
71+
display:"block",
72+
margin:0,
73+
marginTop:theme.spacing(1),
74+
},
75+
6176
actions:{
6277
marginLeft:"auto",
6378
},

‎site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@ import * as API from "../../api/api"
44
import{LanguageasFooterLanguage}from"../../components/FormFooter/FormFooter"
55
import{MockTemplate,MockWorkspace}from"../../testHelpers/entities"
66
import{renderWithAuth}from"../../testHelpers/renderHelpers"
7-
import{LanguageasFormLanguage}from"../../util/formUtils"
87
importCreateWorkspacePagefrom"./CreateWorkspacePage"
98
import{Language}from"./CreateWorkspacePageView"
109

1110
constrenderCreateWorkspacePage=()=>{
1211
returnrenderWithAuth(<CreateWorkspacePage/>,{
13-
route:"/workspaces/new?template="+MockTemplate.name,
14-
path:"/workspaces/new",
12+
route:"/templates/"+MockTemplate.name+"/workspace",
13+
path:"/templates/:template/workspace",
1514
})
1615
}
1716

@@ -29,13 +28,6 @@ describe("CreateWorkspacePage", () => {
2928
expect(element).toBeDefined()
3029
})
3130

32-
it("shows validation error message",async()=>{
33-
renderCreateWorkspacePage()
34-
awaitfillForm({name:"$$$"})
35-
consterrorMessage=awaitscreen.findByText(FormLanguage.nameInvalidChars(Language.nameLabel))
36-
expect(errorMessage).toBeDefined()
37-
})
38-
3931
it("succeeds",async()=>{
4032
renderCreateWorkspacePage()
4133
// You have to spy the method before it is used.

‎site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
import{useMachine}from"@xstate/react"
22
import{FC}from"react"
33
import{Helmet}from"react-helmet"
4-
import{useNavigate,useSearchParams}from"react-router-dom"
5-
import{Template}from"../../api/typesGenerated"
4+
import{useNavigate,useParams}from"react-router-dom"
65
import{useOrganizationId}from"../../hooks/useOrganizationId"
76
import{pageTitle}from"../../util/page"
87
import{createWorkspaceMachine}from"../../xServices/createWorkspace/createWorkspaceXService"
98
import{CreateWorkspacePageView}from"./CreateWorkspacePageView"
109

1110
constCreateWorkspacePage:FC=()=>{
1211
constorganizationId=useOrganizationId()
13-
const[searchParams]=useSearchParams()
14-
constpreSelectedTemplateName=searchParams.get("template")
12+
const{ template}=useParams()
13+
consttemplateName=template ?template :""
1514
constnavigate=useNavigate()
1615
const[createWorkspaceState,send]=useMachine(createWorkspaceMachine,{
17-
context:{ organizationId,preSelectedTemplateName},
16+
context:{ organizationId,templateName},
1817
actions:{
1918
onCreateWorkspace:(_,event)=>{
2019
navigate(`/@${event.data.owner_name}/${event.data.name}`)
@@ -31,24 +30,19 @@ const CreateWorkspacePage: FC = () => {
3130
loadingTemplates={createWorkspaceState.matches("gettingTemplates")}
3231
loadingTemplateSchema={createWorkspaceState.matches("gettingTemplateSchema")}
3332
creatingWorkspace={createWorkspaceState.matches("creatingWorkspace")}
33+
templateName={createWorkspaceState.context.templateName}
3434
templates={createWorkspaceState.context.templates}
3535
selectedTemplate={createWorkspaceState.context.selectedTemplate}
3636
templateSchema={createWorkspaceState.context.templateSchema}
3737
onCancel={()=>{
38-
navigate(preSelectedTemplateName ?"/templates" :"/workspaces")
38+
navigate("/templates")
3939
}}
4040
onSubmit={(request)=>{
4141
send({
4242
type:"CREATE_WORKSPACE",
4343
request,
4444
})
4545
}}
46-
onSelectTemplate={(template:Template)=>{
47-
send({
48-
type:"SELECT_TEMPLATE",
49-
template,
50-
})
51-
}}
5246
/>
5347
</>
5448
)

‎site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,6 @@ export default {
3333

3434
constTemplate:Story<CreateWorkspacePageViewProps>=(args)=><CreateWorkspacePageView{...args}/>
3535

36-
exportconstNoTemplates=Template.bind({})
37-
NoTemplates.args={
38-
templates:[],
39-
}
40-
4136
exportconstNoParameters=Template.bind({})
4237
NoParameters.args={
4338
templates:[MockTemplate],

‎site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx

Lines changed: 13 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
1-
importLinkfrom"@material-ui/core/Link"
2-
importMenuItemfrom"@material-ui/core/MenuItem"
31
import{makeStyles}from"@material-ui/core/styles"
4-
importTextField,{TextFieldProps}from"@material-ui/core/TextField"
5-
importOpenInNewIconfrom"@material-ui/icons/OpenInNew"
2+
importTextFieldfrom"@material-ui/core/TextField"
63
import{FormikContextType,useFormik}from"formik"
74
import{FC,useState}from"react"
8-
import{LinkasRouterLink}from"react-router-dom"
95
import*asYupfrom"yup"
106
import*asTypesGenfrom"../../api/typesGenerated"
11-
import{CodeExample}from"../../components/CodeExample/CodeExample"
12-
import{EmptyState}from"../../components/EmptyState/EmptyState"
137
import{FormFooter}from"../../components/FormFooter/FormFooter"
148
import{FullPageForm}from"../../components/FullPageForm/FullPageForm"
159
import{Loader}from"../../components/Loader/Loader"
@@ -20,29 +14,18 @@ import { getFormHelpers, nameValidator, onChangeTrimmed } from "../../util/formU
2014
exportconstLanguage={
2115
templateLabel:"Template",
2216
nameLabel:"Name",
23-
emptyMessage:"Let's create your first template",
24-
emptyDescription:(
25-
<>
26-
To create a workspace you need to have a template. You can{" "}
27-
<Linktarget="_blank"href="https://github.com/coder/coder/blob/main/docs/templates.md">
28-
create one from scratch
29-
</Link>{" "}
30-
or use a built-in template by typing the following Coder CLI command:
31-
</>
32-
),
33-
templateLink:"Read more about this template",
3417
}
3518

3619
exportinterfaceCreateWorkspacePageViewProps{
3720
loadingTemplates:boolean
3821
loadingTemplateSchema:boolean
3922
creatingWorkspace:boolean
23+
templateName:string
4024
templates?:TypesGen.Template[]
4125
selectedTemplate?:TypesGen.Template
4226
templateSchema?:TypesGen.ParameterSchema[]
4327
onCancel:()=>void
4428
onSubmit:(req:TypesGen.CreateWorkspaceRequest)=>void
45-
onSelectTemplate:(template:TypesGen.Template)=>void
4629
}
4730

4831
exportconstvalidationSchema=Yup.object({
@@ -51,7 +34,8 @@ export const validationSchema = Yup.object({
5134

5235
exportconstCreateWorkspacePageView:FC<CreateWorkspacePageViewProps>=(props)=>{
5336
const[parameterValues,setParameterValues]=useState<Record<string,string>>({})
54-
conststyles=useStyles()
37+
useStyles()
38+
5539
constform:FormikContextType<TypesGen.CreateWorkspaceRequest>=useFormik<TypesGen.CreateWorkspaceRequest>({
5640
initialValues:{
5741
name:"",
@@ -84,75 +68,20 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = (props)
8468
},
8569
})
8670
constgetFieldHelpers=getFormHelpers<TypesGen.CreateWorkspaceRequest>(form)
87-
constselectedTemplate=
88-
props.templates&&
89-
form.values.template_id&&
90-
props.templates.find((template)=>template.id===form.values.template_id)
91-
92-
consthandleTemplateChange:TextFieldProps["onChange"]=(event)=>{
93-
if(!props.templates){
94-
thrownewError("Templates are not loaded")
95-
}
96-
97-
consttemplateId=event.target.value
98-
constselectedTemplate=props.templates.find((template)=>template.id===templateId)
99-
100-
if(!selectedTemplate){
101-
thrownewError(`Template${templateId} not found`)
102-
}
103-
104-
form.setFieldValue("template_id",selectedTemplate.id)
105-
props.onSelectTemplate(selectedTemplate)
106-
}
10771

10872
return(
10973
<FullPageFormtitle="Create workspace"onCancel={props.onCancel}>
11074
<formonSubmit={form.handleSubmit}>
111-
{props.loadingTemplates&&<Loader/>}
112-
11375
<Stack>
114-
{props.templates&&props.templates.length===0&&(
115-
<EmptyState
116-
className={styles.emptyState}
117-
message={Language.emptyMessage}
118-
description={Language.emptyDescription}
119-
descriptionClassName={styles.emptyStateDescription}
120-
cta={
121-
<CodeExampleclassName={styles.code}buttonClassName={styles.codeButton}code="coder template init"/>
122-
}
123-
/>
124-
)}
125-
{props.templates&&props.templates.length>0&&(
126-
<TextField
127-
{...getFieldHelpers("template_id")}
128-
disabled={form.isSubmitting}
129-
onChange={handleTemplateChange}
130-
autoFocus
131-
fullWidth
132-
label={Language.templateLabel}
133-
variant="outlined"
134-
select
135-
helperText={
136-
selectedTemplate&&(
137-
<Link
138-
className={styles.readMoreLink}
139-
component={RouterLink}
140-
to={`/templates/${selectedTemplate.name}`}
141-
target="_blank"
142-
>
143-
{Language.templateLink}<OpenInNewIcon/>
144-
</Link>
145-
)
146-
}
147-
>
148-
{props.templates.map((template)=>(
149-
<MenuItemkey={template.id}value={template.id}>
150-
{template.name}
151-
</MenuItem>
152-
))}
153-
</TextField>
154-
)}
155-
76+
<TextField
77+
disabled
78+
fullWidth
79+
label={Language.templateLabel}
80+
value={props.selectedTemplate?.name||props.templateName}
81+
variant="outlined"
82+
/>
83+
84+
{props.loadingTemplateSchema&&<Loader/>}
15685
{props.selectedTemplate&&props.templateSchema&&(
15786
<>
15887
<TextField

‎site/src/pages/TemplatePage/TemplatePageView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const TemplatePageView: FC<TemplatePageViewProps> = ({ template, activeTe
3939
<Margins>
4040
<PageHeader
4141
actions={
42-
<Linkunderline="none"component={RouterLink}to={`/workspaces/new?template=${template.name}`}>
42+
<Linkunderline="none"component={RouterLink}to={`/templates/${template.name}/workspace`}>
4343
<ButtonstartIcon={<AddCircleOutline/>}>{Language.createButton}</Button>
4444
</Link>
4545
}

‎site/src/pages/TemplatesPage/TemplatesPageView.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
HelpTooltipTitle,
2323
}from"../../components/HelpTooltip/HelpTooltip"
2424
import{Margins}from"../../components/Margins/Margins"
25-
import{PageHeader,PageHeaderTitle}from"../../components/PageHeader/PageHeader"
25+
import{PageHeader,PageHeaderText,PageHeaderTitle}from"../../components/PageHeader/PageHeader"
2626
import{Stack}from"../../components/Stack/Stack"
2727
import{TableLoader}from"../../components/TableLoader/TableLoader"
2828

@@ -84,6 +84,9 @@ export const TemplatesPageView: FC<TemplatesPageViewProps> = (props) => {
8484
<TemplateHelpTooltip/>
8585
</Stack>
8686
</PageHeaderTitle>
87+
{props.templates&&props.templates.length>0&&(
88+
<PageHeaderText>Choose a template to create a new workspace.</PageHeaderText>
89+
)}
8790
</PageHeader>
8891

8992
<Table>

‎site/src/pages/WorkspacesPage/WorkspacesPageView.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
HelpTooltipTitle,
3333
}from"../../components/HelpTooltip/HelpTooltip"
3434
import{Margins}from"../../components/Margins/Margins"
35-
import{PageHeader,PageHeaderTitle}from"../../components/PageHeader/PageHeader"
35+
import{PageHeader,PageHeaderText,PageHeaderTitle}from"../../components/PageHeader/PageHeader"
3636
import{Stack}from"../../components/Stack/Stack"
3737
import{TableLoader}from"../../components/TableLoader/TableLoader"
3838
import{getFormHelpers,onChangeTrimmed}from"../../util/formUtils"
@@ -41,7 +41,7 @@ import { getDisplayStatus, workspaceFilterQuery } from "../../util/workspace"
4141
dayjs.extend(relativeTime)
4242

4343
exportconstLanguage={
44-
createWorkspaceButton:"Createworkspace",
44+
createFromTemplateButton:"Createfrom template",
4545
emptyCreateWorkspaceMessage:"Create your first workspace",
4646
emptyCreateWorkspaceDescription:"Start editing your source code and building your software",
4747
emptyResultsMessage:"No results matched your search",
@@ -132,11 +132,13 @@ export const WorkspacesPageView: FC<WorkspacesPageViewProps> = ({ loading, works
132132
<Margins>
133133
<PageHeader
134134
actions={
135-
<Linkunderline="none"component={RouterLink}to="/workspaces/new">
136-
<ButtonstartIcon={<AddCircleOutline/>}style={{height:"44px"}}>
137-
{Language.createWorkspaceButton}
138-
</Button>
139-
</Link>
135+
<PageHeaderText>
136+
Create a new workspace from a{" "}
137+
<Linkcomponent={RouterLink}to="/templates">
138+
Template
139+
</Link>
140+
.
141+
</PageHeaderText>
140142
}
141143
>
142144
<PageHeaderTitle>
@@ -213,7 +215,7 @@ export const WorkspacesPageView: FC<WorkspacesPageViewProps> = ({ loading, works
213215
description={Language.emptyCreateWorkspaceDescription}
214216
cta={
215217
<Linkunderline="none"component={RouterLink}to="/workspaces/new">
216-
<ButtonstartIcon={<AddCircleOutline/>}>{Language.createWorkspaceButton}</Button>
218+
<ButtonstartIcon={<AddCircleOutline/>}>{Language.createFromTemplateButton}</Button>
217219
</Link>
218220
}
219221
/>

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp