Files
headquarter/apps/web/src/pages/ConfigProfilesPage.tsx
T
Developer 98d4393387 feat: redesign mobile Config Profiles detail, preview, and edit pages
- Rewrite ConfigProfilesMobileView to match desktop functionality:
  full detail view with all fields, preview action showing resolved
  profile, and edit view with project/tool selects, includes,
  environment variables, runtime hints, files, mounts with nested
  files, and git mounts.
- Update useConfigProfiles.handleSubmit to return boolean success.
- Update ConfigProfilesPage to pass required state and callbacks.
- Add mobile-specific CSS for config profile forms, includes,
  mount/file cards, and preview panels.
- Allow MobileDetailView to render extra children.

Quality gates: npm run typecheck, npm run lint, npm test -- --run (87 passed)

Refs: openspec/changes/mobile-config-profiles-ui
2026-06-13 22:04:17 +00:00

202 lines
5.0 KiB
TypeScript

import { useState } from "react";
import { ErrorState, LoadingState } from "../components/data-states";
import { useMobileViewport } from "../hooks/use-mobile-viewport";
import { useConfigProfiles } from "../hooks/use-config-profiles";
import { ConfigProfileListSidebar } from "../components/features/config-profiles/ConfigProfileListSidebar";
import { ConfigProfileEditorPanel } from "../components/features/config-profiles/ConfigProfileEditorPanel";
import { ConfigProfilesMobileView } from "../components/features/config-profiles/ConfigProfilesMobileView";
type MobileView = "list" | "detail" | "edit" | "preview";
export const ConfigProfilesPage = () => {
const isMobile = useMobileViewport();
const [mobileView, setMobileView] = useState<MobileView>("list");
const {
status,
profiles,
projects,
toolTypes,
selectedProfile,
selectedProfileId,
isCreating,
saveStatus,
error,
previewData,
previewingId,
formData,
includedProfileIds,
dragOverIndex,
loadData,
handleSelectProfile,
handleCreateNew,
handleSubmit,
handleDelete,
handlePreview,
updateFormField,
addEnvVar,
updateEnvVar,
removeEnvVar,
addFile,
updateFile,
removeFile,
addMount,
updateMount,
removeMount,
addMountFile,
updateMountFile,
removeMountFile,
getIncludedProfile,
getScopeLabel,
availableProfilesForInclude,
addInclude,
removeInclude,
handleDragStart,
handleDragOver,
handleDragLeave,
handleDrop,
setPreviewData,
setFormData,
setSaveStatus,
setIncludedProfileIds,
populateForm,
} = useConfigProfiles();
const handleReset = () => {
if (isCreating) {
setFormData({
name: "",
description: "",
env_vars: {},
runtime_hints: {},
mounts: [],
git_mounts: [],
files: {},
is_default: false,
});
setIncludedProfileIds([]);
setSaveStatus("idle");
} else if (selectedProfile) {
populateForm(selectedProfile);
}
};
if (status === "loading")
return (
<div className="container">
<LoadingState message="Loading Config Profiles..." />
</div>
);
if (status === "error")
return (
<div className="container">
<ErrorState
message="Failed to load Config Profiles."
onRetry={loadData}
/>
</div>
);
if (isMobile) {
return (
<ConfigProfilesMobileView
profiles={profiles}
projects={projects}
toolTypes={toolTypes}
selectedProfile={selectedProfile}
mobileView={mobileView}
isCreating={isCreating}
formData={formData}
includedProfileIds={includedProfileIds}
availableProfiles={availableProfilesForInclude()}
previewData={previewData}
previewingId={previewingId}
saveStatus={saveStatus}
error={error}
onViewChange={setMobileView}
onSelect={handleSelectProfile}
onCreate={handleCreateNew}
onDelete={handleDelete}
onFormChange={updateFormField}
onSubmit={handleSubmit}
getScopeLabel={getScopeLabel}
getIncludedProfile={getIncludedProfile}
onAddInclude={addInclude}
onRemoveInclude={removeInclude}
onAddEnvVar={addEnvVar}
onUpdateEnvVar={updateEnvVar}
onRemoveEnvVar={removeEnvVar}
onAddFile={addFile}
onUpdateFile={updateFile}
onRemoveFile={removeFile}
onAddMount={addMount}
onUpdateMount={updateMount}
onRemoveMount={removeMount}
onAddMountFile={addMountFile}
onUpdateMountFile={updateMountFile}
onRemoveMountFile={removeMountFile}
onPreview={handlePreview}
onClosePreview={() => setPreviewData(null)}
/>
);
}
return (
<div
className="container"
style={{
display: "flex",
height: "calc(100vh - 4rem)",
gap: 0,
padding: 0,
}}
>
<ConfigProfileListSidebar
profiles={profiles}
selectedProfileId={selectedProfileId}
onSelect={handleSelectProfile}
onCreate={handleCreateNew}
onDelete={handleDelete}
/>
<ConfigProfileEditorPanel
isCreating={isCreating}
selectedProfile={selectedProfile}
formData={formData}
includedProfileIds={includedProfileIds}
dragOverIndex={dragOverIndex}
error={error}
saveStatus={saveStatus}
previewData={previewData}
previewingId={previewingId}
projects={projects}
toolTypes={toolTypes}
availableProfiles={availableProfilesForInclude()}
getIncludedProfile={getIncludedProfile}
getScopeLabel={getScopeLabel}
onFormChange={updateFormField}
onSubmit={handleSubmit}
onReset={handleReset}
onPreview={() => selectedProfile && handlePreview(selectedProfile.id)}
onAddInclude={addInclude}
onRemoveInclude={removeInclude}
onDragStart={handleDragStart}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onAddEnvVar={addEnvVar}
onUpdateEnvVar={updateEnvVar}
onRemoveEnvVar={removeEnvVar}
onAddFile={addFile}
onUpdateFile={updateFile}
onRemoveFile={removeFile}
onAddMount={addMount}
onUpdateMount={updateMount}
onRemoveMount={removeMount}
onAddMountFile={addMountFile}
onUpdateMountFile={updateMountFile}
onRemoveMountFile={removeMountFile}
onClosePreview={() => setPreviewData(null)}
/>
</div>
);
};