import { useState } from "react"; import { MobileListView } from "../mobile/mobile-list-view"; import { MobileDetailView } from "../mobile/mobile-detail-view"; import { MobileEditView } from "../mobile/mobile-edit-view"; import { MobileFAB } from "../mobile/mobile-fab"; import { GitMountEditor } from "../git/git-mount-editor"; import { Icon } from "../../icon"; import type { ConfigProfile, CreateConfigProfileRequest, ResolvedProfile, } from "../../../api/config-profiles"; import type { ProjectWithRepos } from "../../../types"; import type { ToolType } from "../../../api/tool-types"; type MobileView = "list" | "detail" | "edit" | "preview"; interface Props { profiles: ConfigProfile[]; projects: ProjectWithRepos[]; toolTypes: ToolType[]; selectedProfile: ConfigProfile | null; mobileView: MobileView; isCreating: boolean; formData: CreateConfigProfileRequest; includedProfileIds: string[]; availableProfiles: ConfigProfile[]; previewData: ResolvedProfile | null; previewingId: string | null; isRefreshingGitMounts: boolean; isReloadingWorkingCopy: boolean; saveStatus: "idle" | "saving" | "saved" | "error"; error: string | null; onViewChange: (view: MobileView) => void; onSelect: (profile: ConfigProfile) => void; onCreate: () => void; onDelete: (id: string) => void; onFormChange: ( key: K, value: CreateConfigProfileRequest[K], ) => void; onSubmit: (e?: React.FormEvent) => Promise; getScopeLabel: (profile: ConfigProfile) => string; getIncludedProfile: (id: string) => ConfigProfile | undefined; onAddInclude: (id: string) => void; onRemoveInclude: (index: number) => void; onAddEnvVar: () => void; onUpdateEnvVar: (oldKey: string, newKey: string, value: string) => void; onRemoveEnvVar: (key: string) => void; onAddFile: () => void; onUpdateFile: (oldPath: string, newPath: string, content: string) => void; onRemoveFile: (path: string) => void; onAddMount: () => void; onUpdateMount: ( index: number, updates: Partial, ) => void; onRemoveMount: (index: number) => void; onAddMountFile: (mountIndex: number) => void; onUpdateMountFile: ( mountIndex: number, oldPath: string, newPath: string, content: string, ) => void; onRemoveMountFile: (mountIndex: number, path: string) => void; onPreview: (id: string) => void; onReloadWorkingCopy: () => void; onRefreshGitMounts: (id: string) => void; onClosePreview: () => void; } export const ConfigProfilesMobileView = ({ profiles, projects, toolTypes, selectedProfile, mobileView, isCreating, formData, includedProfileIds, availableProfiles, previewData, previewingId, isRefreshingGitMounts, isReloadingWorkingCopy, saveStatus, error, onViewChange, onSelect, onCreate, onDelete, onFormChange, onSubmit, getScopeLabel, getIncludedProfile, onAddInclude, onRemoveInclude, onAddEnvVar, onUpdateEnvVar, onRemoveEnvVar, onAddFile, onUpdateFile, onRemoveFile, onAddMount, onUpdateMount, onRemoveMount, onAddMountFile, onUpdateMountFile, onRemoveMountFile, onPreview, onReloadWorkingCopy, onRefreshGitMounts, onClosePreview, }: Props) => { const [isSaving, setIsSaving] = useState(false); const handleSave = async () => { setIsSaving(true); try { const ok = await onSubmit(); if (ok) { onViewChange(isCreating ? "list" : "edit"); } } finally { setIsSaving(false); } }; const handleDeleteClick = () => { if (selectedProfile) { onDelete(selectedProfile.id); onViewChange("list"); } }; const projectName = (id?: string) => projects.find((p) => p.id === id)?.name || id || "None"; const toolTypeName = (id?: string) => toolTypes.find((t) => t.id === id)?.display_name || id || "None"; if (mobileView === "list") { return (

Config Profiles

{profiles.length} profiles
({ id: profile.id, title: profile.name, subtitle: profile.description || getScopeLabel(profile), }))} onItemClick={(id: string) => { const profile = profiles.find((p) => p.id === id); if (profile) { onSelect(profile); onViewChange("edit"); } }} onItemDelete={(id: string) => onDelete(id)} emptyMessage="No config profiles yet" /> { onCreate(); onViewChange("edit"); }} />
); } if (mobileView === "preview" && selectedProfile && previewData) { return (

Resolved Preview

{previewData.profile_name}

{previewData.included_profiles.length > 0 && (
    {previewData.included_profiles.map((p) => (
  • {p.name}
  • ))}
)} {Object.keys(previewData.env_vars).length > 0 && (
								{JSON.stringify(previewData.env_vars, null, 2)}
							
)} {Object.keys(previewData.runtime_hints).length > 0 && (
								{JSON.stringify(previewData.runtime_hints, null, 2)}
							
)} {previewData.mounts.length > 0 && (
{previewData.mounts.map((m, idx) => (
{m.target} ({m.mode}) {Object.keys(m.files).length > 0 && (
											{JSON.stringify(m.files, null, 2)}
										
)}
))}
)} {previewData.git_mounts.length > 0 && (
{previewData.git_mounts.map((m, idx) => (
{m.remote_url} {m.branch ? ` @${m.branch}` : ""} {m.mappings.map((mapping, mi) => (
{mapping.source_path || "."} →{" "} {mapping.target_path}
))}
))}
)} {Object.keys(previewData.files).length > 0 && (
								{JSON.stringify(previewData.files, null, 2)}
							
)} {Object.keys(previewData.overrides.env_vars).length > 0 && (
								{JSON.stringify(previewData.overrides, null, 2)}
							
)}
); } if (mobileView === "detail" && selectedProfile) { const fields = [ { label: "Name", value: selectedProfile.name }, { label: "Description", value: selectedProfile.description || null, }, { label: "Scope", value: getScopeLabel(selectedProfile) }, { label: "Project", value: projectName(selectedProfile.project_id || undefined) }, { label: "Tool Type", value: toolTypeName(selectedProfile.tool_type_id || undefined) }, { label: "Default", value: selectedProfile.is_default, type: "boolean" as const, }, { label: "Includes", value: selectedProfile.includes.length > 0 ? selectedProfile.includes .map( (inc) => getIncludedProfile(inc.included_profile_id)?.name || inc.included_profile_id, ) .join(", ") : null, }, { label: "Environment Variables", value: Object.keys(selectedProfile.env_vars).length > 0 ? Object.entries(selectedProfile.env_vars) .map(([k, v]) => `${k}=${v}`) .join(", ") : null, }, { label: "Runtime Hints", value: Object.keys(selectedProfile.runtime_hints).length > 0 ? JSON.stringify(selectedProfile.runtime_hints, null, 2) : null, type: "code" as const, }, { label: "Files", value: Object.keys(selectedProfile.files).length > 0 ? Object.keys(selectedProfile.files).join(", ") : null, }, { label: "Mounts", value: selectedProfile.mounts.length > 0 ? selectedProfile.mounts .map((m) => `${m.target} (${m.mode})`) .join(", ") : null, }, { label: "Git Mounts", value: selectedProfile.git_mounts.length > 0 ? selectedProfile.git_mounts .map((m) => m.remote_url) .join(", ") : null, }, ]; return ( onViewChange("list")} onEdit={() => { onSelect(selectedProfile); onViewChange("edit"); }} onDelete={handleDeleteClick} >
); } if (mobileView === "edit") { return ( { onViewChange("list"); }} onSave={handleSave} onDelete={isCreating ? undefined : handleDeleteClick} deleteLabel="Delete Profile" isSaving={isSaving || saveStatus === "saving"} >
onFormChange("name", e.target.value)} placeholder="e.g., Development Environment" className="mobile-form-input" required />
onFormChange("description", e.target.value || undefined) } placeholder="Optional description" className="mobile-form-input" />

Includes

{includedProfileIds.length} included
{includedProfileIds.length === 0 ? (

No profiles included. Add profiles to compose configurations.

) : (
{includedProfileIds.map((profileId, index) => { const profile = getIncludedProfile(profileId); if (!profile) return null; return (
{profile.name} {getScopeLabel(profile)}
); })}
)} {availableProfiles.length > 0 && ( )}

Environment Variables

{Object.entries(formData.env_vars || {}).map(([key, value], idx) => (
onUpdateEnvVar(key, e.target.value, value) } placeholder="VAR_NAME" className="mobile-form-input" /> onUpdateEnvVar(key, key, e.target.value) } placeholder="value" className="mobile-form-input" />
))}

Runtime Hints