feat: make session creation a sequential workflow
- Refactor CreateSessionForm into step-by-step workflow - Steps unlock sequentially: Project → Repository → Tool → Clone Mode → Branch - Add visual step indicators with numbered badges - Disable controls until prerequisites are met - Add CSS for workflow step styling
This commit is contained in:
@@ -80,7 +80,11 @@ export const CreateSessionForm = ({
|
||||
const loadBranches = async () => {
|
||||
setIsLoadingBranches(true);
|
||||
try {
|
||||
const branchList = await listRepositoryBranches(selectedRepo);
|
||||
const branchResponse = await listRepositoryBranches(
|
||||
fixedProjectId || selectedProject,
|
||||
selectedRepo
|
||||
);
|
||||
const branchList = branchResponse.branches;
|
||||
setBranches(branchList);
|
||||
const defaultBranch = branchList.find((b) => b.is_default);
|
||||
if (defaultBranch) {
|
||||
@@ -94,7 +98,7 @@ export const CreateSessionForm = ({
|
||||
}
|
||||
};
|
||||
void loadBranches();
|
||||
}, [selectedRepo, showCloneMode]);
|
||||
}, [selectedRepo, showCloneMode, selectedProject, fixedProjectId]);
|
||||
|
||||
// Filter repositories by selected project
|
||||
const availableRepos = selectedProject
|
||||
@@ -167,6 +171,32 @@ export const CreateSessionForm = ({
|
||||
|
||||
const isSubmitting = status === "creating";
|
||||
|
||||
// Determine which steps are active/unlocked
|
||||
const hasProject = !!(fixedProjectId || selectedProject);
|
||||
const hasRepo = !!(fixedRepoId || selectedRepo);
|
||||
const hasToolType = !!selectedToolType;
|
||||
|
||||
const renderStep = (
|
||||
label: string,
|
||||
number: number,
|
||||
isActive: boolean,
|
||||
isComplete: boolean,
|
||||
children: React.ReactNode
|
||||
) => {
|
||||
const stepClass = `workflow-step ${isActive ? "active" : ""} ${isComplete ? "complete" : ""}`;
|
||||
return (
|
||||
<div className={stepClass}>
|
||||
<div className="workflow-step-header">
|
||||
<span className="workflow-step-number">{number}</span>
|
||||
<span className="workflow-step-label">{label}</span>
|
||||
</div>
|
||||
<div className="workflow-step-content">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`create-session-form-wrapper ${className}`}>
|
||||
{isSubmitting && (
|
||||
@@ -178,11 +208,11 @@ export const CreateSessionForm = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="stack create-session-form">
|
||||
<div className="form-row">
|
||||
{fixedProjectId && showFixedFields ? (
|
||||
<form onSubmit={handleSubmit} className="stack create-session-form workflow-form">
|
||||
{/* Step 1: Project */}
|
||||
{renderStep("Select Project", 1, true, hasProject,
|
||||
fixedProjectId && showFixedFields ? (
|
||||
<label className="form-field">
|
||||
Project
|
||||
<input
|
||||
type="text"
|
||||
value={projectName || projects.find((p) => p.id === fixedProjectId)?.name || ""}
|
||||
@@ -192,13 +222,15 @@ export const CreateSessionForm = ({
|
||||
</label>
|
||||
) : (
|
||||
<label className="form-field">
|
||||
Project
|
||||
<select
|
||||
value={selectedProject}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setSelectedProject(value);
|
||||
setSelectedRepo("");
|
||||
setSelectedToolType("");
|
||||
setCloneMode("mount");
|
||||
setIsCreatingNewBranch(false);
|
||||
onProjectChange?.(value);
|
||||
}}
|
||||
disabled={isSubmitting}
|
||||
@@ -211,11 +243,13 @@ export const CreateSessionForm = ({
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
)}
|
||||
)
|
||||
)}
|
||||
|
||||
{fixedRepoId && showFixedFields ? (
|
||||
{/* Step 2: Repository */}
|
||||
{hasProject && renderStep("Select Repository", 2, true, hasRepo,
|
||||
fixedRepoId && showFixedFields ? (
|
||||
<label className="form-field">
|
||||
Repository
|
||||
<input
|
||||
type="text"
|
||||
value={repoName || repositories.find((r) => r.id === fixedRepoId)?.name || ""}
|
||||
@@ -225,11 +259,15 @@ export const CreateSessionForm = ({
|
||||
</label>
|
||||
) : (
|
||||
<label className="form-field">
|
||||
Repository
|
||||
<select
|
||||
value={selectedRepo}
|
||||
onChange={(e) => setSelectedRepo(e.target.value)}
|
||||
disabled={!selectedProject || isSubmitting}
|
||||
onChange={(e) => {
|
||||
setSelectedRepo(e.target.value);
|
||||
setSelectedToolType("");
|
||||
setCloneMode("mount");
|
||||
setIsCreatingNewBranch(false);
|
||||
}}
|
||||
disabled={!hasProject || isSubmitting}
|
||||
>
|
||||
<option value="">Select repository...</option>
|
||||
{availableRepos.map((r) => (
|
||||
@@ -239,14 +277,20 @@ export const CreateSessionForm = ({
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
)}
|
||||
)
|
||||
)}
|
||||
|
||||
{/* Step 3: Tool Type */}
|
||||
{hasRepo && renderStep("Select Tool", 3, true, hasToolType,
|
||||
<label className="form-field">
|
||||
Tool Type
|
||||
<select
|
||||
value={selectedToolType}
|
||||
onChange={(e) => setSelectedToolType(e.target.value)}
|
||||
disabled={isSubmitting}
|
||||
onChange={(e) => {
|
||||
setSelectedToolType(e.target.value);
|
||||
setCloneMode("mount");
|
||||
setIsCreatingNewBranch(false);
|
||||
}}
|
||||
disabled={!hasRepo || isSubmitting}
|
||||
>
|
||||
<option value="">Select tool...</option>
|
||||
{toolTypes.map((t) => (
|
||||
@@ -256,12 +300,12 @@ export const CreateSessionForm = ({
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showCloneMode && (
|
||||
{/* Step 4: Clone Mode & Branch */}
|
||||
{showCloneMode && hasToolType && renderStep("Repository Access", 4, true, false,
|
||||
<div className="form-row">
|
||||
<label className="form-field">
|
||||
Repository Access
|
||||
<div className="radio-group">
|
||||
<label className="radio-label">
|
||||
<input
|
||||
@@ -269,7 +313,10 @@ export const CreateSessionForm = ({
|
||||
name="cloneMode"
|
||||
value="mount"
|
||||
checked={cloneMode === "mount"}
|
||||
onChange={(e) => setCloneMode(e.target.value as "mount" | "clone")}
|
||||
onChange={(e) => {
|
||||
setCloneMode(e.target.value as "mount" | "clone");
|
||||
setIsCreatingNewBranch(false);
|
||||
}}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
Mount (live sync)
|
||||
@@ -280,7 +327,10 @@ export const CreateSessionForm = ({
|
||||
name="cloneMode"
|
||||
value="clone"
|
||||
checked={cloneMode === "clone"}
|
||||
onChange={(e) => setCloneMode(e.target.value as "mount" | "clone")}
|
||||
onChange={(e) => {
|
||||
setCloneMode(e.target.value as "mount" | "clone");
|
||||
setIsCreatingNewBranch(false);
|
||||
}}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
Clone fresh copy
|
||||
@@ -376,48 +426,53 @@ export const CreateSessionForm = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<label className="form-field">
|
||||
Display Name (optional)
|
||||
<input
|
||||
type="text"
|
||||
value={displayName}
|
||||
onChange={(e) => setDisplayName(e.target.value)}
|
||||
placeholder="My Development Environment"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</label>
|
||||
{/* Step 5: Display Name */}
|
||||
{hasToolType && renderStep("Display Name (optional)", 5, true, !!displayName,
|
||||
<label className="form-field">
|
||||
<input
|
||||
type="text"
|
||||
value={displayName}
|
||||
onChange={(e) => setDisplayName(e.target.value)}
|
||||
placeholder="My Development Environment"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</label>
|
||||
)}
|
||||
|
||||
{/* Error & Submit */}
|
||||
{error && <p className="error-text">{error}</p>}
|
||||
|
||||
<div className="form-actions">
|
||||
{onCancel && (
|
||||
{hasToolType && (
|
||||
<div className="form-actions">
|
||||
{onCancel && (
|
||||
<button
|
||||
className="secondary-button"
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="secondary-button"
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
className="primary-button"
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
Cancel
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<Icon name="loading" size="sm" />
|
||||
Creating...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Icon name="add" size="sm" />
|
||||
{submitLabel}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="primary-button"
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<Icon name="loading" size="sm" />
|
||||
Creating...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Icon name="add" size="sm" />
|
||||
{submitLabel}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user