@@ -5,17 +5,22 @@ import RadioGroup from "@mui/material/RadioGroup";
55import { API } from "api/api" ;
66import type { Template , TemplateVersionParameter } from "api/typesGenerated" ;
77import { FormSection , VerticalForm } from "components/Form/Form" ;
8+ import { Input } from "components/Input/Input" ;
9+ import { Label } from "components/Label/Label" ;
810import { Loader } from "components/Loader/Loader" ;
911import { RichParameterInput } from "components/RichParameterInput/RichParameterInput" ;
12+ import { useDebouncedFunction } from "hooks/debounce" ;
1013import { useClipboard } from "hooks/useClipboard" ;
1114import { CheckIcon , CopyIcon } from "lucide-react" ;
1215import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout" ;
13- import { type FC , useEffect , useState } from "react" ;
16+ import { type FC , useEffect , useId , useState } from "react" ;
1417import { Helmet } from "react-helmet-async" ;
1518import { useQuery } from "react-query" ;
19+ import { nameValidator } from "utils/formUtils" ;
1620import { pageTitle } from "utils/page" ;
1721import { getInitialRichParameterValues } from "utils/richParameters" ;
1822import { paramsUsedToCreateWorkspace } from "utils/workspace" ;
23+ import { ValidationError } from "yup" ;
1924
2025type ButtonValues = Record < string , string > ;
2126
@@ -47,19 +52,25 @@ interface TemplateEmbedPageViewProps {
4752templateParameters ?:TemplateVersionParameter [ ] ;
4853}
4954
55+ const deploymentUrl = `${ window . location . protocol } //${ window . location . host } ` ;
56+
5057function getClipboardCopyContent (
5158templateName :string ,
5259organization :string ,
5360buttonValues :ButtonValues | undefined ,
5461) :string {
55- const deploymentUrl = `${ window . location . protocol } //${ window . location . host } ` ;
5662const createWorkspaceUrl = `${ deploymentUrl } /templates/${ organization } /${ templateName } /workspace` ;
5763const createWorkspaceParams = new URLSearchParams ( buttonValues ) ;
64+ if ( createWorkspaceParams . get ( "name" ) === "" ) {
65+ createWorkspaceParams . delete ( "name" ) ; // no default workspace name if empty
66+ }
5867const buttonUrl = `${ createWorkspaceUrl } ?${ createWorkspaceParams . toString ( ) } ` ;
5968
6069return `[](${ buttonUrl } )` ;
6170}
6271
72+ const workspaceNameValidator = nameValidator ( "Workspace name" ) ;
73+
6374export const TemplateEmbedPageView :FC < TemplateEmbedPageViewProps > = ( {
6475template,
6576templateParameters,
@@ -79,6 +90,7 @@ export const TemplateEmbedPageView: FC<TemplateEmbedPageViewProps> = ({
7990if ( templateParameters && ! buttonValues ) {
8091const buttonValues :ButtonValues = {
8192mode :"manual" ,
93+ name :"" ,
8294} ;
8395for ( const parameter of getInitialRichParameterValues (
8496templateParameters ,
@@ -89,6 +101,27 @@ export const TemplateEmbedPageView: FC<TemplateEmbedPageViewProps> = ({
89101}
90102} , [ buttonValues , templateParameters ] ) ;
91103
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+
92125return (
93126< >
94127< Helmet >
@@ -126,6 +159,29 @@ export const TemplateEmbedPageView: FC<TemplateEmbedPageViewProps> = ({
126159</ RadioGroup >
127160</ FormSection >
128161
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+
129185{ templateParameters . length > 0 && (
130186< div
131187css = { { display :"flex" , flexDirection :"column" , gap :36 } }