Close create wizard modal as soon as ddev config succeeds

handleSubmit awaited the entire post-create chain (start, then for
WordPress: download core + install) before ever calling onClose(),
so the modal overlay sat there for the whole multi-step process with
no way to see what was actually happening — the terminal panel that
already tracks this operation's live output was hidden behind it.
Close right after the quick config step succeeds and let post-create
continue as a tracked background operation; failures still surface
via the existing toast/terminal-panel error handling regardless of
modal state.

Also extracts the project-name validation helpers out of
CreateProjectModal.tsx into their own module — mixing component and
non-component exports in one file breaks React Fast Refresh.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
reaper
2026-08-08 05:53:10 -05:00
co-authored by Claude Sonnet 5
parent 5beb70f1e2
commit c2b95d057f
3 changed files with 28 additions and 20 deletions
@@ -19,25 +19,11 @@ import { getTypeLabel, PROJECT_TYPES } from './types/registry'
import { GenericSetup } from './types/GenericSetup'
import { WordpressSetup } from './types/WordpressSetup'
import type { TypeSetupHandle } from './types/shared'
import { isValidProjectName, slugifyProjectName } from './projectName'
import docksideIcon from '../../assets/dockside-icon.png'
type Step = 'site' | 'setup'
// DDEV project names must be valid hostname labels: alphanumeric and
// hyphens only, can't start/end with a hyphen.
const PROJECT_NAME_PATTERN = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/
export function isValidProjectName(name: string): boolean {
return PROJECT_NAME_PATTERN.test(name)
}
export function slugifyProjectName(raw: string): string {
return raw
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
}
const fieldClass =
'w-full rounded-lg border border-neutral-300 bg-white/80 px-3 py-2 text-sm shadow-sm transition placeholder:text-neutral-400 focus:border-cyan-400 dark:border-white/10 dark:bg-neutral-950/70 dark:placeholder:text-neutral-600'
@@ -92,12 +78,20 @@ export function CreateProjectModal({ onClose }: { onClose: () => void }): React.
setIsSubmitting(true)
try {
await createProject.mutateAsync({ directory, projectName: name, projectType, docroot })
await setupRef.current?.runPostCreate({ directory, projectName: name })
} catch {
setIsSubmitting(false)
return
}
// Post-create (starting the project, downloading/installing WordPress,
// etc.) can run long. Close the modal as soon as the quick `ddev
// config` step succeeds instead of blocking the whole wizard on it —
// the terminal panel already tracks and surfaces this operation's
// progress and any failure independently of the modal.
const runPostCreate = setupRef.current?.runPostCreate
selectProject(name)
onClose()
} finally {
setIsSubmitting(false)
}
runPostCreate?.({ directory, projectName: name }).catch(() => {})
}
return (
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { isValidProjectName, slugifyProjectName } from './CreateProjectModal'
import { isValidProjectName, slugifyProjectName } from './projectName'
describe('isValidProjectName', () => {
it('accepts hostname-safe names', () => {
@@ -0,0 +1,14 @@
// DDEV project names must be valid hostname labels: alphanumeric and
// hyphens only, can't start/end with a hyphen.
const PROJECT_NAME_PATTERN = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/
export function isValidProjectName(name: string): boolean {
return PROJECT_NAME_PATTERN.test(name)
}
export function slugifyProjectName(raw: string): string {
return raw
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
}