moadim 3.2.5

Loop engine for AI agents — routines over REST, MCP, and a built-in web UI
import { useMemo, useState } from "react";
import type { RoutineResponse } from "../../api/hooks";

export interface MoveRoutineDialogProps {
  routine: RoutineResponse;
  saving: boolean;
  onCancel: () => void;
  onConfirm: (folder: string | undefined, slug: string) => void;
}

function splitFolder(value: string | null | undefined): string {
  return value?.trim() ?? "";
}

function splitSlug(value: string | undefined): string {
  return value?.trim() ?? "";
}

function locationPath(folder: string, slug: string): string {
  return folder === "" ? slug : `${folder}/${slug}`;
}

export function MoveRoutineDialog({ routine, saving, onCancel, onConfirm }: MoveRoutineDialogProps) {
  const [folder, setFolder] = useState(splitFolder(routine.folder));
  const [slug, setSlug] = useState(splitSlug(routine.slug));
  const folderValue = folder.trim();
  const slugValue = slug.trim();
  const relPath = useMemo(() => locationPath(folderValue, slugValue), [folderValue, slugValue]);
  const currentPath = useMemo(
    () => locationPath(splitFolder(routine.folder), splitSlug(routine.slug)),
    [routine.folder, routine.slug],
  );
  const unchanged = folderValue === splitFolder(routine.folder) && slugValue === splitSlug(routine.slug);
  const invalid = slugValue === "" || slugValue.includes("/") || slugValue.includes("\\");

  return (
    <div className="overlay" onClick={onCancel}>
      <div
        className="dialog move-dialog"
        role="dialog"
        aria-modal="true"
        aria-labelledby="move-dialog-title"
        onClick={(e) => e.stopPropagation()}
      >
        <div className="move-dialog-hd">
          <div>
            <div id="move-dialog-title" className="dialog-title move-dialog-title">FOLDER MANAGEMENT</div>
            <div className="move-dialog-sub">Move routine files without changing the display title.</div>
          </div>
          <div className="move-dialog-badge">FILES</div>
        </div>

        <div className="move-current-card" aria-label={`Current path ${currentPath}`}>
          <div className="move-current-label">CURRENT LOCATION</div>
          <div className="move-current-row">
            <span className="routine-fs-icon" aria-hidden="true">📄</span>
            <div className="move-current-copy">
              <strong>{routine.title}</strong>
              <span>{currentPath || "root"}</span>
            </div>
          </div>
        </div>

        <div className="move-path-grid">
          <div className="form-group move-form-group">
            <label className="form-label" htmlFor="move-folder">
              FOLDER
            </label>
            <input
              id="move-folder"
              className="form-input move-input"
              placeholder="maintenance"
              autoComplete="off"
              spellCheck={false}
              value={folder}
              onChange={(e) => setFolder(e.target.value)}
            />
            <div className="form-hint">Blank keeps the routine at root. Use / for nested folders.</div>
          </div>
          <div className="form-group move-form-group">
            <label className="form-label" htmlFor="move-slug">
              SLUG*
            </label>
            <input
              id="move-slug"
              className="form-input move-input"
              placeholder="daily-maintenance-digest"
              autoComplete="off"
              spellCheck={false}
              value={slug}
              onChange={(e) => setSlug(e.target.value)}
            />
            <div className="form-hint">One path segment only. Title remains display-only.</div>
          </div>
        </div>

        <div className="move-preview" aria-label={`Destination path routines/${relPath || "…"}/routine.toml`}>
          <div className="move-preview-label">DESTINATION PREVIEW</div>
          <div className="move-preview-path">routines/{relPath || "…"}/routine.toml</div>
        </div>

        <div className="dialog-actions move-dialog-actions">
          <button type="button" className="btn btn-ghost" onClick={onCancel}>
            CANCEL
          </button>
          <button
            type="button"
            className="btn btn-primary"
            disabled={saving || invalid || unchanged}
            onClick={() => onConfirm(folderValue === "" ? undefined : folderValue, slugValue)}
          >
            {saving ? "MOVING…" : "MOVE"}
          </button>
        </div>
      </div>
    </div>
  );
}