mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-11 17:07:40 +00:00
- Replace `src/App.jsx` with `src/App.tsx` and move route-level UI orchestration into `src/components/app/AppContent.tsx`. This separates provider/bootstrap concerns from runtime app layout logic, keeps route definitions minimal, and improves readability of the root app entry. - Introduce `src/hooks/useProjectsState.ts` to centralize project/session/sidebar state management previously embedded in `App.jsx`. This keeps the existing behavior for: project loading, Cursor session hydration, WebSocket `loading_progress` handling, additive-update protection for active sessions, URL-based session selection, sidebar refresh/delete/new-session flows. The hook now exposes a typed `sidebarSharedProps` contract and typed handlers used by `AppContent`. - Introduce `src/hooks/useSessionProtection.ts` for active/processing session lifecycle logic. This preserves session-protection behavior while isolating `activeSessions`, `processingSessions`, and temporary-session replacement into a dedicated reusable hook. - Replace monolithic `src/components/Sidebar.jsx` with typed `src/components/Sidebar.tsx` as a thin orchestrator. `Sidebar.tsx` now focuses on wiring controller state/actions, modal visibility, collapsed mode, and version modal behavior instead of rendering every UI branch inline. - Add `src/hooks/useSidebarController.ts` to encapsulate sidebar interaction/state logic. This includes expand/collapse state, inline project/session editing state, project starring/sorting/filtering, lazy session pagination, delete confirmations, rename/delete actions, refresh state, and mobile touch click handling. - Add strongly typed sidebar domain models in `src/components/sidebar/types.ts` and move sidebar-derived helpers into `src/components/sidebar/utils.ts`. Utility coverage now includes: session provider normalization, session view-model creation (name/time/activity/message count), project sorting/filtering, task indicator status derivation, starred-project persistence and readbacks. - Split sidebar rendering into focused components under `src/components/sidebar/`: `SidebarContent.tsx` for top-level sidebar layout composition. `SidebarProjectList.tsx` for project-state branching and project iteration. `SidebarProjectsState.tsx` for loading/empty/no-search-result placeholders. `SidebarProjectItem.tsx` for per-project desktop/mobile header rendering and actions. `SidebarProjectSessions.tsx` for expanded session area, skeletons, pagination, and new-session controls. `SidebarSessionItem.tsx` for per-session desktop/mobile item rendering and session actions. `SessionProviderIcon.tsx` for provider icon normalization. `SidebarHeader.tsx`, `SidebarFooter.tsx`, `SidebarCollapsed.tsx`, and `SidebarModals.tsx` as dedicated typed UI surfaces. This keeps rendering responsibilities local and significantly improves traceability. - Convert shared UI primitives from JSX to TSX: `src/components/ui/button.tsx`, `src/components/ui/input.tsx`, `src/components/ui/badge.tsx`, `src/components/ui/scroll-area.tsx`. These now provide typed props/variants (`forwardRef` where appropriate) while preserving existing class/behavior. - Add shared app typings in `src/types/app.ts` for projects/sessions/websocket/loading contracts used by new hooks/components. - Add global window declarations in `src/types/global.d.ts` for `__ROUTER_BASENAME__`, `refreshProjects`, and `openSettings`, removing implicit `any` usage for global integration points. - Update `src/main.jsx` to import `App.tsx` and keep app bootstrap consistent with the TS migration. - Update `src/components/QuickSettingsPanel.jsx` to self-resolve mobile state via `useDeviceSettings` (remove `isMobile` prop dependency), and update `src/components/ChatInterface.jsx` to render `QuickSettingsPanel` directly. This reduces prop drilling and keeps quick settings colocated with chat UI concerns.
188 lines
7.1 KiB
TypeScript
188 lines
7.1 KiB
TypeScript
import { FolderPlus, MessageSquare, RefreshCw, Search, X } from 'lucide-react';
|
|
import type { TFunction } from 'i18next';
|
|
import { Button } from '../ui/button';
|
|
import { Input } from '../ui/input';
|
|
import { IS_PLATFORM } from '../../constants/config';
|
|
|
|
type SidebarHeaderProps = {
|
|
isPWA: boolean;
|
|
isMobile: boolean;
|
|
isLoading: boolean;
|
|
projectsCount: number;
|
|
searchFilter: string;
|
|
onSearchFilterChange: (value: string) => void;
|
|
onClearSearchFilter: () => void;
|
|
onRefresh: () => void;
|
|
isRefreshing: boolean;
|
|
onCreateProject: () => void;
|
|
onCollapseSidebar: () => void;
|
|
t: TFunction;
|
|
};
|
|
|
|
export default function SidebarHeader({
|
|
isPWA,
|
|
isMobile,
|
|
isLoading,
|
|
projectsCount,
|
|
searchFilter,
|
|
onSearchFilterChange,
|
|
onClearSearchFilter,
|
|
onRefresh,
|
|
isRefreshing,
|
|
onCreateProject,
|
|
onCollapseSidebar,
|
|
t,
|
|
}: SidebarHeaderProps) {
|
|
return (
|
|
<>
|
|
<div
|
|
className="md:p-4 md:border-b md:border-border"
|
|
style={isPWA && isMobile ? { paddingTop: '44px' } : {}}
|
|
>
|
|
<div className="hidden md:flex items-center justify-between">
|
|
{IS_PLATFORM ? (
|
|
<a
|
|
href="https://cloudcli.ai/dashboard"
|
|
className="flex items-center gap-3 hover:opacity-80 transition-opacity group"
|
|
title={t('tooltips.viewEnvironments')}
|
|
>
|
|
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center shadow-sm group-hover:shadow-md transition-shadow">
|
|
<MessageSquare className="w-4 h-4 text-primary-foreground" />
|
|
</div>
|
|
<div>
|
|
<h1 className="text-lg font-bold text-foreground">{t('app.title')}</h1>
|
|
<p className="text-sm text-muted-foreground">{t('app.subtitle')}</p>
|
|
</div>
|
|
</a>
|
|
) : (
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center shadow-sm">
|
|
<MessageSquare className="w-4 h-4 text-primary-foreground" />
|
|
</div>
|
|
<div>
|
|
<h1 className="text-lg font-bold text-foreground">{t('app.title')}</h1>
|
|
<p className="text-sm text-muted-foreground">{t('app.subtitle')}</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-8 w-8 px-0 hover:bg-accent transition-colors duration-200"
|
|
onClick={onCollapseSidebar}
|
|
title={t('tooltips.hideSidebar')}
|
|
>
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
|
</svg>
|
|
</Button>
|
|
</div>
|
|
|
|
<div
|
|
className="md:hidden p-3 border-b border-border"
|
|
style={isPWA && isMobile ? { paddingTop: '16px' } : {}}
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
{IS_PLATFORM ? (
|
|
<a
|
|
href="https://cloudcli.ai/dashboard"
|
|
className="flex items-center gap-3 active:opacity-70 transition-opacity"
|
|
title={t('tooltips.viewEnvironments')}
|
|
>
|
|
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
|
|
<MessageSquare className="w-4 h-4 text-primary-foreground" />
|
|
</div>
|
|
<div>
|
|
<h1 className="text-lg font-semibold text-foreground">{t('app.title')}</h1>
|
|
<p className="text-sm text-muted-foreground">{t('projects.title')}</p>
|
|
</div>
|
|
</a>
|
|
) : (
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
|
|
<MessageSquare className="w-4 h-4 text-primary-foreground" />
|
|
</div>
|
|
<div>
|
|
<h1 className="text-lg font-semibold text-foreground">{t('app.title')}</h1>
|
|
<p className="text-sm text-muted-foreground">{t('projects.title')}</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex gap-2">
|
|
<button
|
|
className="w-8 h-8 rounded-md bg-background border border-border flex items-center justify-center active:scale-95 transition-all duration-150"
|
|
onClick={onRefresh}
|
|
disabled={isRefreshing}
|
|
>
|
|
<RefreshCw className={`w-4 h-4 text-foreground ${isRefreshing ? 'animate-spin' : ''}`} />
|
|
</button>
|
|
<button
|
|
className="w-8 h-8 rounded-md bg-primary text-primary-foreground flex items-center justify-center active:scale-95 transition-all duration-150"
|
|
onClick={onCreateProject}
|
|
>
|
|
<FolderPlus className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{!isLoading && !isMobile && (
|
|
<div className="px-3 md:px-4 py-2 border-b border-border">
|
|
<div className="flex gap-2">
|
|
<Button
|
|
variant="default"
|
|
size="sm"
|
|
className="flex-1 h-8 text-xs bg-primary hover:bg-primary/90 transition-all duration-200"
|
|
onClick={onCreateProject}
|
|
title={t('tooltips.createProject')}
|
|
>
|
|
<FolderPlus className="w-3.5 h-3.5 mr-1.5" />
|
|
{t('projects.newProject')}
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
className="h-8 w-8 px-0 hover:bg-accent transition-colors duration-200 group"
|
|
onClick={onRefresh}
|
|
disabled={isRefreshing}
|
|
title={t('tooltips.refresh')}
|
|
>
|
|
<RefreshCw
|
|
className={`w-3.5 h-3.5 ${
|
|
isRefreshing ? 'animate-spin' : 'group-hover:rotate-180 transition-transform duration-300'
|
|
}`}
|
|
/>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{projectsCount > 0 && !isLoading && (
|
|
<div className="px-3 md:px-4 py-2 border-b border-border">
|
|
<div className="relative">
|
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
|
<Input
|
|
type="text"
|
|
placeholder={t('projects.searchPlaceholder')}
|
|
value={searchFilter}
|
|
onChange={(event) => onSearchFilterChange(event.target.value)}
|
|
className="pl-9 h-9 text-sm bg-muted/50 border-0 focus:bg-background focus:ring-1 focus:ring-primary/20"
|
|
/>
|
|
{searchFilter && (
|
|
<button
|
|
onClick={onClearSearchFilter}
|
|
className="absolute right-2 top-1/2 transform -translate-y-1/2 p-1 hover:bg-accent rounded"
|
|
>
|
|
<X className="w-3 h-3 text-muted-foreground" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
}
|