Files
ThothII/frontend/src/shell/WorkspacePublishDialog.tsx
T

93 lines
4.1 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 { useEffect, useState } from "react";
import {
asWorkspaceApiError,
publishWorkspace,
validateWorkspace,
type PublishWorkspaceRequest,
type WorkspaceRevision,
} from "../api/workspaces";
import { Button } from "../components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../components/ui/dialog";
export interface WorkspacePublishDialogProps {
open: boolean;
request: PublishWorkspaceRequest;
onOpenChange: (open: boolean) => void;
onPublished: (revision: WorkspaceRevision | undefined) => void;
}
function RevisionSummary({ request }: { request: PublishWorkspaceRequest }) {
return <p className="rounded-md border border-border/70 bg-muted/40 px-3 py-2 font-mono text-xs text-muted-foreground">New workspace · base {request.baseCommit.slice(0, 12)}</p>;
}
export function WorkspacePublishDialog({ open, request, onOpenChange, onPublished }: WorkspacePublishDialogProps) {
const [validatedRequest, setValidatedRequest] = useState<PublishWorkspaceRequest>();
const [confirming, setConfirming] = useState(false);
const [message, setMessage] = useState<string>();
const [publishing, setPublishing] = useState(false);
useEffect(() => {
if (!open) return;
setValidatedRequest(undefined);
setConfirming(false);
setMessage(undefined);
setPublishing(false);
}, [open, request]);
async function validateDraft() {
setMessage(undefined);
try {
const result = await validateWorkspace(request.workspace);
setValidatedRequest({ ...request, workspace: result.workspace });
setMessage("Workspace definition is valid.");
} catch (error) {
const safe = asWorkspaceApiError(error);
setMessage(safe ? `${safe.code}: ${safe.message}` : "workspace_invalid: Validation could not be completed");
}
}
async function createWorkspace() {
if (!validatedRequest) return;
setPublishing(true);
setMessage(undefined);
try {
const result = await publishWorkspace(validatedRequest);
onPublished(result?.revision);
onOpenChange(false);
} catch (error) {
const safe = asWorkspaceApiError(error);
setMessage(safe ? `${safe.code}: ${safe.message}` : "workspace_invalid: Publication could not be completed");
setConfirming(false);
} finally {
setPublishing(false);
}
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent showCloseButton={false} className="max-w-[min(94vw,46rem)] p-0">
<DialogHeader className="border-b border-border/70 px-5 pb-4 pt-5 pr-12">
<DialogTitle>{confirming ? "Confirm workspace creation" : "Create workspace"}</DialogTitle>
<DialogDescription>{confirming ? "This creates the validated workspace definition in the shared Git registry." : "Validation and an explicit confirmation are required before this shared definition is created."}</DialogDescription>
</DialogHeader>
<div className="grid gap-4 px-5 py-5">
<RevisionSummary request={validatedRequest ?? request} />
{message && <p role="status" className="rounded-md border border-border/70 bg-muted/40 px-3 py-2 text-sm">{message}</p>}
{confirming ? (
<DialogFooter>
<Button variant="outline" onClick={() => setConfirming(false)}>Cancel</Button>
<Button onClick={() => { void createWorkspace(); }} disabled={publishing}>{publishing ? "Creating…" : "Confirm create"}</Button>
</DialogFooter>
) : (
<div className="flex flex-wrap justify-end gap-2">
<Button variant="outline" onClick={() => { void validateDraft(); }}>Validate draft</Button>
<Button disabled={!validatedRequest} onClick={() => setConfirming(true)}>Create workspace</Button>
</div>
)}
</div>
<button type="button" aria-label="Close publish workspace" onClick={() => onOpenChange(false)} className="absolute right-3 top-3 grid size-7 place-items-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground">×</button>
</DialogContent>
</Dialog>
);
}