Files
pieced-portal/src/components/packages/workspace-editor.tsx
admin 7c4e20099d
All checks were successful
Build and Push / build (push) Successful in 1m24s
Role split and owner gating
2026-04-26 22:45:38 +02:00

97 lines
3.2 KiB
TypeScript

"use client";
import { useTranslations } from "next-intl";
import { useState } from "react";
const FILE_TABS = ["SOUL.md", "AGENTS.md", "TOOLS.md"] as const;
interface Props {
tenantName: string;
files: Record<string, string>;
/** Slice 5: when false, save button hidden and textarea is read-only. */
canEdit?: boolean;
}
export function WorkspaceEditor({ tenantName, files, canEdit = true }: Props) {
const t = useTranslations("workspace");
const [activeTab, setActiveTab] = useState<string>("SOUL.md");
const [localFiles, setLocalFiles] = useState<Record<string, string>>(files);
const [saving, setSaving] = useState(false);
const [dirty, setDirty] = useState(false);
const [error, setError] = useState<string | null>(null);
function handleChange(content: string) {
if (!canEdit) return;
setLocalFiles((prev) => ({ ...prev, [activeTab]: content }));
setDirty(true);
}
async function handleSave() {
setSaving(true);
setError(null);
try {
const res = await fetch(`/api/tenants/${tenantName}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ workspaceFiles: localFiles }),
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.error || "Save failed");
}
setDirty(false);
} catch (e: any) {
setError(e.message);
} finally {
setSaving(false);
}
}
return (
<div className="bg-surface-1 border border-border rounded-xl overflow-hidden">
<div className="flex items-center justify-between border-b border-border px-4 py-2">
<div className="flex gap-1">
{FILE_TABS.map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={`rounded-md px-2.5 py-1 text-xs font-mono transition-colors cursor-pointer ${
activeTab === tab
? "bg-surface-3 text-accent"
: "text-text-muted hover:text-text-secondary"
}`}
>
{tab}
</button>
))}
</div>
{canEdit && (
<button
onClick={handleSave}
disabled={!dirty || saving}
className="rounded-lg bg-accent px-3 py-1 text-xs font-medium text-surface-0 hover:bg-accent-dim disabled:opacity-40 cursor-pointer"
>
{saving ? "…" : t("save")}
</button>
)}
</div>
<textarea
value={localFiles[activeTab] || ""}
onChange={(e) => handleChange(e.target.value)}
readOnly={!canEdit}
spellCheck={false}
className={`w-full min-h-[300px] resize-y bg-transparent p-4 font-mono text-sm text-text-secondary placeholder:text-text-muted focus:outline-none ${
!canEdit ? "cursor-default" : ""
}`}
placeholder={t("placeholder", { file: activeTab })}
/>
<div className="border-t border-border px-4 py-2 flex items-center justify-between">
<p className="text-[10px] text-text-muted leading-relaxed">{t("seedingNote")}</p>
{error && <p className="text-[10px] text-error">{error}</p>}
</div>
</div>
);
}