diff --git a/src/renderer/src/components/create/CreateProjectModal.tsx b/src/renderer/src/components/create/CreateProjectModal.tsx index 59e58cf..1bae70d 100644 --- a/src/renderer/src/components/create/CreateProjectModal.tsx +++ b/src/renderer/src/components/create/CreateProjectModal.tsx @@ -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 }) - selectProject(name) - onClose() - } finally { + } 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() + runPostCreate?.({ directory, projectName: name }).catch(() => {}) } return ( diff --git a/src/renderer/src/components/create/projectName.test.ts b/src/renderer/src/components/create/projectName.test.ts index 63168fb..5e0b3d0 100644 --- a/src/renderer/src/components/create/projectName.test.ts +++ b/src/renderer/src/components/create/projectName.test.ts @@ -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', () => { diff --git a/src/renderer/src/components/create/projectName.ts b/src/renderer/src/components/create/projectName.ts new file mode 100644 index 0000000..1f28590 --- /dev/null +++ b/src/renderer/src/components/create/projectName.ts @@ -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, '') +}