import { type FormEvent, type ReactElement, useState } from 'react' import { useLocation } from 'wouter' import { useCreateWorkspace, useWorkspaceSetupInfo } from '../api' import { useAppConfig } from '@/client/api/app-config' import { Button } from '@/client/components/ui/button' import { Dialog, DialogDescription, DialogTitle, DialogTrigger } from '@/client/components/ui/dialog' import { Input } from '@/client/components/ui/input' import { Tooltip, TooltipContent, TooltipTrigger } from '@/client/components/ui/tooltip' import { validateWorkspaceFolderName } from '@/lib/workspace-name' import { WORKSPACE_TYPE_ORDER } from '@/lib/workspace-types' import type { WorkspaceType } from '@/lib/types' import { DemoDialog } from './DemoDialog' import { WorkspaceAgentStep } from './WorkspaceAgentStep' import { WorkspaceDialogContent } from './WorkspaceDialogContent' type CreateWorkspaceDialogProps = { trigger: ReactElement } type CreateWorkspaceStep = 'agent' | 'name' const DEFAULT_WORKSPACE_TYPE = WORKSPACE_TYPE_ORDER[0] export function CreateWorkspaceDialog({ trigger }: CreateWorkspaceDialogProps) { const [, navigate] = useLocation() const appConfig = useAppConfig() const info = useWorkspaceSetupInfo() const createMutation = useCreateWorkspace() const [open, setOpen] = useState(false) const [step, setStep] = useState('agent') const [type, setType] = useState(DEFAULT_WORKSPACE_TYPE) const [name, setName] = useState('') const trimmedName = name.trim() const nameError = trimmedName ? validateWorkspaceFolderName(trimmedName) : null const isCreating = createMutation.isPending // Cloud demo: creating workspaces is off — the same trigger opens the // cloud-demo promo dialog instead. After every hook so the hook order is stable. if (appConfig.cloudDemo) { return } function finish(workspaceId: string) { setOpen(false) navigate(`/workspace/${workspaceId}`) } function resetDialog() { setStep('agent') setType(DEFAULT_WORKSPACE_TYPE) setName('') createMutation.reset() } function continueToName() { createMutation.reset() setStep('name') } function createWorkspace() { if (!trimmedName || nameError || isCreating) return createMutation.reset() createMutation.mutate({ name: trimmedName, type }, { onSuccess: entry => finish(entry.id) }) } return ( { if (!nextOpen) resetDialog() }} > {step === 'agent' ? ( } primaryLabel="Next" onTypeChange={setType} onSubmit={continueToName} /> ) : ( )} ) } type WorkspaceNameStepProps = { name: string validationError: string | null requestError?: string isPending: boolean onNameChange: (name: string) => void onSubmit: () => void } function WorkspaceNameStep({ name, validationError, requestError, isPending, onNameChange, onSubmit }: WorkspaceNameStepProps) { function handleSubmit(event: FormEvent) { event.preventDefault() onSubmit() } return (
Create new workspace Give it a short and recognizable name
onNameChange(event.target.value)} placeholder="my-workspace" autoFocus aria-invalid={Boolean(validationError)} aria-describedby={validationError ? 'workspace-name-error' : undefined} autoComplete="off" spellCheck={false} /> {validationError && ( )}
{requestError && (

{requestError}

)}
) } // Importing a folder from the app is on hold: the native picker only ever ran on // macOS and cannot work at all when moi is opened from another device. The // button stays as the signpost for the console route. function ExistingFolderButton() { const button = ( ) return ( {/* The popup lays its children out in a row — one span keeps the copy flowing as a sentence with the command inline. */} Not supported yet — run{' '} moi init {' '} in the folder from your console to add it ) }