Files
fusion/packages/dashboard/app/components/NativeShellConnectionManager.tsx
Fusion 025eb7b3ae feat(FN-3398): update desktop README with multi-project setup and troublesh
The merge lands Step 7 of FN-3398, adding documentation and delivery artifacts across the mobile and desktop packages with updated READMEs, mobile-specific docs, and architecture references.

Fusion-Task-Id: FN-3398
2026-05-04 22:17:10 -07:00

93 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useMemo, useState } from "react";
import type { FusionShellApi, ShellConnectionProfile, ShellConnectionState } from "../types/native-shell";
import "./NativeShellConnectionManager.css";
interface NativeShellConnectionManagerProps {
open: boolean;
shellApi: FusionShellApi;
shellState: ShellConnectionState;
onClose: () => void;
}
export function NativeShellConnectionManager({ open, shellApi, shellState, onClose }: NativeShellConnectionManagerProps) {
const activeProfile = useMemo(
() => shellState.profiles.find((profile) => profile.id === shellState.activeProfileId) ?? null,
[shellState.activeProfileId, shellState.profiles],
);
const [draft, setDraft] = useState<Partial<ShellConnectionProfile>>({});
const [error, setError] = useState<string | null>(null);
if (!open) return null;
const workingName = draft.name ?? activeProfile?.name ?? "";
const workingUrl = draft.serverUrl ?? activeProfile?.serverUrl ?? "";
const workingToken = draft.authToken ?? activeProfile?.authToken ?? "";
const saveCurrent = async () => {
setError(null);
try {
const saved = await shellApi.saveProfile({
id: activeProfile?.id,
name: workingName || "Remote Server",
serverUrl: workingUrl,
authToken: workingToken || null,
});
await shellApi.setActiveProfile(saved.id);
setDraft({});
} catch (nextError) {
setError((nextError as Error).message);
}
};
return (
<div className="modal-overlay open">
<div className="modal native-shell-connection-manager" role="dialog" aria-label="Connection Manager">
<div className="modal-header">
<h2>Connection Manager</h2>
<button type="button" className="modal-close" onClick={onClose} aria-label="Close">
×
</button>
</div>
{shellState.host === "desktop-shell" && (
<div className="native-shell-connection-manager__mode-row">
<button type="button" className={`btn ${shellState.desktopMode === "local" ? "btn-primary" : ""}`} onClick={() => void shellApi.setDesktopMode("local")}>Local</button>
<button type="button" className={`btn ${shellState.desktopMode !== "local" ? "btn-primary" : ""}`} onClick={() => void shellApi.setDesktopMode("remote")}>Remote</button>
</div>
)}
<div className="native-shell-connection-manager__profiles">
{shellState.profiles.map((profile) => (
<div className="card native-shell-connection-manager__profile" key={profile.id}>
<div>
<strong>{profile.name}</strong>
<div className="settings-muted">{profile.serverUrl}</div>
</div>
<div className="native-shell-connection-manager__profile-actions">
<button type="button" className="btn btn-sm" onClick={() => setDraft(profile)}>Edit</button>
<button type="button" className="btn btn-sm" onClick={() => void shellApi.setActiveProfile(profile.id)}>Use</button>
<button type="button" className="btn btn-sm btn-danger" onClick={() => void shellApi.deleteProfile(profile.id)}>Delete</button>
</div>
</div>
))}
</div>
<div className="form-group native-shell-connection-manager__editor">
<label htmlFor="native-shell-connection-manager-name">Name</label>
<input id="native-shell-connection-manager-name" className="input" value={workingName} onChange={(event) => setDraft((value) => ({ ...value, name: event.target.value }))} />
<label htmlFor="native-shell-connection-manager-url">Server URL</label>
<input id="native-shell-connection-manager-url" className="input" value={workingUrl} onChange={(event) => setDraft((value) => ({ ...value, serverUrl: event.target.value }))} />
<label htmlFor="native-shell-connection-manager-token">Auth token (optional)</label>
<input id="native-shell-connection-manager-token" className="input" value={workingToken ?? ""} onChange={(event) => setDraft((value) => ({ ...value, authToken: event.target.value }))} />
{error && <p className="form-error">{error}</p>}
</div>
<div className="modal-actions">
<button type="button" className="btn" onClick={onClose}>Close</button>
<button type="button" className="btn btn-primary" onClick={() => void saveCurrent()} disabled={!workingUrl.trim()}>Save</button>
</div>
</div>
</div>
);
}