refactor(web): simplify NewSession component layout with divided sections

Replace Card wrapper with a flex column layout using dividers for better visual separation and simpler structure.
This commit is contained in:
weishu
2025-12-23 15:53:42 +08:00
parent 422018299c
commit e637f2bb27
+108 -118
View File
@@ -2,7 +2,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
import type { ApiClient } from '@/api/client'
import type { Machine } from '@/types/api'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { usePlatform } from '@/hooks/usePlatform'
import { useSpawnSession } from '@/hooks/mutations/useSpawnSession'
import { useRecentPaths } from '@/hooks/useRecentPaths'
@@ -105,127 +104,118 @@ export function NewSession(props: {
const canCreate = machineId && directory.trim() && !isFormDisabled
return (
<div className="p-3">
<Card>
<CardHeader className="pb-2">
<CardTitle>Create Session</CardTitle>
</CardHeader>
<CardContent className="pt-0">
<div className="flex flex-col gap-4">
{/* Machine Selector */}
<div className="flex flex-col gap-1.5">
<label className="text-xs font-medium text-[var(--app-hint)]">
Machine
</label>
<select
value={machineId ?? ''}
onChange={(e) => handleMachineChange(e.target.value)}
disabled={isFormDisabled}
className="w-full rounded-md border border-[var(--app-border)] bg-[var(--app-bg)] p-2 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--app-link)] disabled:opacity-50"
>
{props.isLoading && (
<option value="">Loading machines...</option>
)}
{!props.isLoading && props.machines.length === 0 && (
<option value="">No machines available</option>
)}
{props.machines.map((m) => (
<option key={m.id} value={m.id}>
{getMachineTitle(m)}
{m.metadata?.platform ? ` (${m.metadata.platform})` : ''}
</option>
))}
</select>
</div>
<div className="flex flex-col divide-y divide-[var(--app-divider)]">
{/* Machine Selector */}
<div className="flex flex-col gap-1.5 px-3 py-3">
<label className="text-xs font-medium text-[var(--app-hint)]">
Machine
</label>
<select
value={machineId ?? ''}
onChange={(e) => handleMachineChange(e.target.value)}
disabled={isFormDisabled}
className="w-full rounded-md border border-[var(--app-border)] bg-[var(--app-bg)] p-2 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--app-link)] disabled:opacity-50"
>
{props.isLoading && (
<option value="">Loading machines...</option>
)}
{!props.isLoading && props.machines.length === 0 && (
<option value="">No machines available</option>
)}
{props.machines.map((m) => (
<option key={m.id} value={m.id}>
{getMachineTitle(m)}
{m.metadata?.platform ? ` (${m.metadata.platform})` : ''}
</option>
))}
</select>
</div>
{/* Directory Input */}
<div className="flex flex-col gap-1.5">
<label className="text-xs font-medium text-[var(--app-hint)]">
Directory
</label>
<input
type="text"
placeholder="/path/to/project"
value={directory}
onChange={(e) => setDirectory(e.target.value)}
disabled={isFormDisabled}
className="w-full rounded-md border border-[var(--app-border)] bg-[var(--app-bg)] p-2 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--app-link)] disabled:opacity-50"
/>
{/* Directory Input */}
<div className="flex flex-col gap-1.5 px-3 py-3">
<label className="text-xs font-medium text-[var(--app-hint)]">
Directory
</label>
<input
type="text"
placeholder="/path/to/project"
value={directory}
onChange={(e) => setDirectory(e.target.value)}
disabled={isFormDisabled}
className="w-full rounded-md border border-[var(--app-border)] bg-[var(--app-bg)] p-2 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--app-link)] disabled:opacity-50"
/>
{/* Recent Paths */}
{recentPaths.length > 0 && (
<div className="flex flex-col gap-1 mt-1">
<span className="text-xs text-[var(--app-hint)]">Recent:</span>
<div className="flex flex-wrap gap-1">
{recentPaths.map((path) => (
<button
key={path}
type="button"
onClick={() => handlePathClick(path)}
disabled={isFormDisabled}
className="rounded bg-[var(--app-subtle-bg)] px-2 py-1 text-xs text-[var(--app-fg)] hover:bg-[var(--app-secondary-bg)] transition-colors truncate max-w-[200px] disabled:opacity-50"
title={path}
>
{path}
</button>
))}
</div>
</div>
)}
</div>
{/* Agent Selector */}
<div className="flex flex-col gap-1.5">
<label className="text-xs font-medium text-[var(--app-hint)]">
Agent
</label>
<div className="flex gap-3">
{(['claude', 'codex', 'gemini'] as const).map((agentType) => (
<label
key={agentType}
className="flex items-center gap-1.5 cursor-pointer"
>
<input
type="radio"
name="agent"
value={agentType}
checked={agent === agentType}
onChange={() => setAgent(agentType)}
disabled={isFormDisabled}
className="accent-[var(--app-link)]"
/>
<span className="text-sm capitalize">{agentType}</span>
</label>
))}
</div>
</div>
{/* Error Message */}
{(error ?? spawnError) ? (
<div className="text-sm text-red-600">
{error ?? spawnError}
</div>
) : null}
{/* Action Buttons */}
<div className="flex gap-2 pt-2">
<Button
variant="secondary"
onClick={props.onCancel}
disabled={isFormDisabled}
>
Cancel
</Button>
<Button
onClick={handleCreate}
disabled={!canCreate}
>
{isPending ? 'Creating...' : 'Create'}
</Button>
{/* Recent Paths */}
{recentPaths.length > 0 && (
<div className="flex flex-col gap-1 mt-1">
<span className="text-xs text-[var(--app-hint)]">Recent:</span>
<div className="flex flex-wrap gap-1">
{recentPaths.map((path) => (
<button
key={path}
type="button"
onClick={() => handlePathClick(path)}
disabled={isFormDisabled}
className="rounded bg-[var(--app-subtle-bg)] px-2 py-1 text-xs text-[var(--app-fg)] hover:bg-[var(--app-secondary-bg)] transition-colors truncate max-w-[200px] disabled:opacity-50"
title={path}
>
{path}
</button>
))}
</div>
</div>
</CardContent>
</Card>
)}
</div>
{/* Agent Selector */}
<div className="flex flex-col gap-1.5 px-3 py-3">
<label className="text-xs font-medium text-[var(--app-hint)]">
Agent
</label>
<div className="flex gap-3">
{(['claude', 'codex', 'gemini'] as const).map((agentType) => (
<label
key={agentType}
className="flex items-center gap-1.5 cursor-pointer"
>
<input
type="radio"
name="agent"
value={agentType}
checked={agent === agentType}
onChange={() => setAgent(agentType)}
disabled={isFormDisabled}
className="accent-[var(--app-link)]"
/>
<span className="text-sm capitalize">{agentType}</span>
</label>
))}
</div>
</div>
{/* Error Message */}
{(error ?? spawnError) ? (
<div className="px-3 py-2 text-sm text-red-600">
{error ?? spawnError}
</div>
) : null}
{/* Action Buttons */}
<div className="flex gap-2 px-3 py-3">
<Button
variant="secondary"
onClick={props.onCancel}
disabled={isFormDisabled}
>
Cancel
</Button>
<Button
onClick={handleCreate}
disabled={!canCreate}
>
{isPending ? 'Creating...' : 'Create'}
</Button>
</div>
</div>
)
}