mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
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:
+108
-118
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user