@@ -5,17 +5,22 @@ import RadioGroup from "@mui/material/RadioGroup";
5
5
import { API } from "api/api" ;
6
6
import type { Template , TemplateVersionParameter } from "api/typesGenerated" ;
7
7
import { FormSection , VerticalForm } from "components/Form/Form" ;
8
+ import { Input } from "components/Input/Input" ;
9
+ import { Label } from "components/Label/Label" ;
8
10
import { Loader } from "components/Loader/Loader" ;
9
11
import { RichParameterInput } from "components/RichParameterInput/RichParameterInput" ;
12
+ import { useDebouncedFunction } from "hooks/debounce" ;
10
13
import { useClipboard } from "hooks/useClipboard" ;
11
14
import { CheckIcon , CopyIcon } from "lucide-react" ;
12
15
import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout" ;
13
- import { type FC , useEffect , useState } from "react" ;
16
+ import { type FC , useEffect , useId , useState } from "react" ;
14
17
import { Helmet } from "react-helmet-async" ;
15
18
import { useQuery } from "react-query" ;
19
+ import { nameValidator } from "utils/formUtils" ;
16
20
import { pageTitle } from "utils/page" ;
17
21
import { getInitialRichParameterValues } from "utils/richParameters" ;
18
22
import { paramsUsedToCreateWorkspace } from "utils/workspace" ;
23
+ import { ValidationError } from "yup" ;
19
24
20
25
type ButtonValues = Record < string , string > ;
21
26
@@ -47,19 +52,25 @@ interface TemplateEmbedPageViewProps {
47
52
templateParameters ?: TemplateVersionParameter [ ] ;
48
53
}
49
54
55
+ const deploymentUrl = `${ window . location . protocol } //${ window . location . host } ` ;
56
+
50
57
function getClipboardCopyContent (
51
58
templateName : string ,
52
59
organization : string ,
53
60
buttonValues : ButtonValues | undefined ,
54
61
) : string {
55
- const deploymentUrl = `${ window . location . protocol } //${ window . location . host } ` ;
56
62
const createWorkspaceUrl = `${ deploymentUrl } /templates/${ organization } /${ templateName } /workspace` ;
57
63
const createWorkspaceParams = new URLSearchParams ( buttonValues ) ;
64
+ if ( createWorkspaceParams . get ( "name" ) === "" ) {
65
+ createWorkspaceParams . delete ( "name" ) ; // no default workspace name if empty
66
+ }
58
67
const buttonUrl = `${ createWorkspaceUrl } ?${ createWorkspaceParams . toString ( ) } ` ;
59
68
60
69
return `[](${ buttonUrl } )` ;
61
70
}
62
71
72
+ const workspaceNameValidator = nameValidator ( "Workspace name" ) ;
73
+
63
74
export const TemplateEmbedPageView : FC < TemplateEmbedPageViewProps > = ( {
64
75
template,
65
76
templateParameters,
@@ -79,6 +90,7 @@ export const TemplateEmbedPageView: FC<TemplateEmbedPageViewProps> = ({
79
90
if ( templateParameters && ! buttonValues ) {
80
91
const buttonValues : ButtonValues = {
81
92
mode : "manual" ,
93
+ name : "" ,
82
94
} ;
83
95
for ( const parameter of getInitialRichParameterValues (
84
96
templateParameters ,
@@ -89,6 +101,27 @@ export const TemplateEmbedPageView: FC<TemplateEmbedPageViewProps> = ({
89
101
}
90
102
} , [ buttonValues , templateParameters ] ) ;
91
103
104
+ const [ workspaceNameError , setWorkspaceNameError ] = useState ( "" ) ;
105
+ const validateWorkspaceName = ( workspaceName : string ) => {
106
+ try {
107
+ if ( workspaceName ) {
108
+ workspaceNameValidator . validateSync ( workspaceName ) ;
109
+ }
110
+ setWorkspaceNameError ( "" ) ;
111
+ } catch ( e ) {
112
+ if ( e instanceof ValidationError ) {
113
+ setWorkspaceNameError ( e . message ) ;
114
+ }
115
+ }
116
+ } ;
117
+ const { debounced : debouncedValidateWorkspaceName } = useDebouncedFunction (
118
+ validateWorkspaceName ,
119
+ 500 ,
120
+ ) ;
121
+
122
+ const hookId = useId ( ) ;
123
+ const defaultWorkspaceNameID = `${ hookId } -default-workspace-name` ;
124
+
92
125
return (
93
126
< >
94
127
< Helmet >
@@ -126,6 +159,29 @@ export const TemplateEmbedPageView: FC<TemplateEmbedPageViewProps> = ({
126
159
</ RadioGroup >
127
160
</ FormSection >
128
161
162
+ < div className = "flex flex-col gap-1" >
163
+ < Label className = "text-md" htmlFor = { defaultWorkspaceNameID } >
164
+ Workspace name
165
+ </ Label >
166
+ < div className = "text-sm text-content-secondary pb-3" >
167
+ Default name for the new workspace
168
+ </ div >
169
+ < Input
170
+ id = { defaultWorkspaceNameID }
171
+ value = { buttonValues . name }
172
+ onChange = { ( event ) => {
173
+ debouncedValidateWorkspaceName ( event . target . value ) ;
174
+ setButtonValues ( ( buttonValues ) => ( {
175
+ ...buttonValues ,
176
+ name : event . target . value ,
177
+ } ) ) ;
178
+ } }
179
+ />
180
+ < div className = "text-sm text-highlight-red mt-1" role = "alert" >
181
+ { workspaceNameError }
182
+ </ div >
183
+ </ div >
184
+
129
185
{ templateParameters . length > 0 && (
130
186
< div
131
187
css = { { display : "flex" , flexDirection : "column" , gap : 36 } }
0 commit comments