mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-15 19:07:23 +00:00
refactor(wizard): rebuild project creation flow as modular TypeScript components
Replace the monolithic `ProjectCreationWizard.jsx` with a feature-based TS
implementation under `src/components/project-creation-wizard`, while preserving
existing behavior and improving readability, maintainability, and state isolation.
Why:
- The previous wizard mixed API logic, flow state, folder browsing, and UI in one file.
- Refactoring and testing were difficult due to tightly coupled concerns.
- We needed stronger type safety and localized component state.
What changed:
- Deleted:
- `src/components/ProjectCreationWizard.jsx`
- Added new modular structure:
- `src/components/project-creation-wizard/index.ts`
- `src/components/project-creation-wizard/ProjectCreationWizard.tsx`
- `src/components/project-creation-wizard/types.ts`
- `src/components/project-creation-wizard/data/workspaceApi.ts`
- `src/components/project-creation-wizard/hooks/useGithubTokens.ts`
- `src/components/project-creation-wizard/utils/pathUtils.ts`
- `src/components/project-creation-wizard/components/*`
- `WizardProgress`, `WizardFooter`, `ErrorBanner`
- `StepTypeSelection`, `StepConfiguration`, `StepReview`
- `WorkspacePathField`, `GithubAuthenticationCard`, `FolderBrowserModal`
- Updated import usage:
- `src/components/sidebar/view/subcomponents/SidebarModals.tsx`
now imports from `../../../project-creation-wizard`.
Implementation details:
- Migrated wizard logic to TypeScript using `type` aliases only.
- Kept component prop types colocated in each component file.
- Split responsibilities by feature:
- container/orchestration in `ProjectCreationWizard.tsx`
- API/SSE and request parsing in `data/workspaceApi.ts`
- GitHub token loading/caching behavior in `useGithubTokens`
- path/URL helpers in `utils/pathUtils.ts`
- Localized UI-only state to child components:
- folder browser modal state (current path, hidden folders, create-folder input)
- path suggestion dropdown state with debounced lookup
- Preserved existing UX flows:
- step navigation and validation
- existing/new workspace modes
- optional GitHub clone + auth modes
- clone progress via SSE
- folder browsing + folder creation
- Added focused comments for non-obvious logic (debounce, SSE auth constraint, path edge cases).
This commit is contained in:
52
src/components/project-creation-wizard/utils/pathUtils.ts
Normal file
52
src/components/project-creation-wizard/utils/pathUtils.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { WorkspaceType } from '../types';
|
||||
|
||||
const SSH_PREFIXES = ['git@', 'ssh://'];
|
||||
const WINDOWS_DRIVE_PATTERN = /^[A-Za-z]:\\?$/;
|
||||
|
||||
export const isSshGitUrl = (url: string): boolean => {
|
||||
const trimmedUrl = url.trim();
|
||||
return SSH_PREFIXES.some((prefix) => trimmedUrl.startsWith(prefix));
|
||||
};
|
||||
|
||||
export const shouldShowGithubAuthentication = (
|
||||
workspaceType: WorkspaceType,
|
||||
githubUrl: string,
|
||||
): boolean => workspaceType === 'new' && githubUrl.trim().length > 0 && !isSshGitUrl(githubUrl);
|
||||
|
||||
export const isCloneWorkflow = (workspaceType: WorkspaceType, githubUrl: string): boolean =>
|
||||
workspaceType === 'new' && githubUrl.trim().length > 0;
|
||||
|
||||
export const getSuggestionRootPath = (inputPath: string): string => {
|
||||
const trimmedPath = inputPath.trim();
|
||||
const lastSeparatorIndex = Math.max(trimmedPath.lastIndexOf('/'), trimmedPath.lastIndexOf('\\'));
|
||||
if (lastSeparatorIndex === 2 && /^[A-Za-z]:/.test(trimmedPath)) {
|
||||
return `${trimmedPath.slice(0, 2)}\\`;
|
||||
}
|
||||
|
||||
return lastSeparatorIndex > 0 ? trimmedPath.slice(0, lastSeparatorIndex) : '~';
|
||||
};
|
||||
|
||||
// Handles root edge cases for Unix-like and Windows paths.
|
||||
export const getParentPath = (currentPath: string): string | null => {
|
||||
if (currentPath === '~' || currentPath === '/' || WINDOWS_DRIVE_PATTERN.test(currentPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const lastSeparatorIndex = Math.max(currentPath.lastIndexOf('/'), currentPath.lastIndexOf('\\'));
|
||||
if (lastSeparatorIndex <= 0) {
|
||||
return '/';
|
||||
}
|
||||
|
||||
if (lastSeparatorIndex === 2 && /^[A-Za-z]:/.test(currentPath)) {
|
||||
return `${currentPath.slice(0, 2)}\\`;
|
||||
}
|
||||
|
||||
return currentPath.slice(0, lastSeparatorIndex);
|
||||
};
|
||||
|
||||
export const joinFolderPath = (basePath: string, folderName: string): string => {
|
||||
const normalizedBasePath = basePath.trim().replace(/[\\/]+$/, '');
|
||||
const separator =
|
||||
normalizedBasePath.includes('\\') && !normalizedBasePath.includes('/') ? '\\' : '/';
|
||||
return `${normalizedBasePath}${separator}${folderName.trim()}`;
|
||||
};
|
||||
Reference in New Issue
Block a user