React Component

/**
 * DropZone root layout component.
 *
 * This component composes the main drop-zone view by arranging resource,
 * service, certification, tooling, hosting, contribution, and media widgets
 * into responsive Bootstrap grid sections.
 *
 * :returns: A JSX fragment containing the complete drop-zone composition.
 * :rtype: JSX.Element
 *
 * :example:
 *
 *     <DropZone sessionContext={sessionContext} />
 */
import { useEffect, useRef, useState } from "react";
import { load } from "js-yaml";

import { fetchDjangoUrl } from "@smarter/common";
import type { SessionContext } from "@smarter/common";

import { loggerPrefix } from "@/const";
import DropZoneModal from "@/components/Modal";

import "./styles.css";

type DropZoneProps = {
  sessionContext: SessionContext;
};

type Manifest = Record<string, any>;

type ModalState = {
  open: boolean;
  title: string;
  data?: any;
  isError?: boolean;
};


export default function DropZone({ sessionContext }: DropZoneProps) {
  const fileInputRef = useRef<HTMLInputElement>(null);

  const [isDragActive, setIsDragActive] = useState(false);
  const [isUploading, setIsUploading] = useState(false);

  const [modal, setModal] = useState<ModalState>({
    open: false,
    title: "",
    data: null,
    isError: false,
  });

  const openFileDialog = () => fileInputRef.current?.click();

  const showModal = (title: string, data: any, isError = false) => {
    setModal({
      open: true,
      title,
      data,
      isError,
    });
  };

  useEffect(() => {
    const onDragOver = (e: DragEvent) => {
      e.preventDefault();
      setIsDragActive(true);
    };

    const onDragLeave = (e: DragEvent) => {
      e.preventDefault();
      if (e.clientX === 0 && e.clientY === 0) {
        setIsDragActive(false);
      }
    };

    const onDrop = (e: DragEvent) => {
      e.preventDefault();
      setIsDragActive(false);
    };

    window.addEventListener("dragover", onDragOver);
    window.addEventListener("dragleave", onDragLeave);
    window.addEventListener("drop", onDrop);

    return () => {
      window.removeEventListener("dragover", onDragOver);
      window.removeEventListener("dragleave", onDragLeave);
      window.removeEventListener("drop", onDrop);
    };
  }, []);

  function validateManifest(manifest: Manifest) {
    if (!manifest || typeof manifest !== "object") {
      throw new Error("Manifest is not a valid object");
    }

    if (!manifest.metadata?.name) {
      throw new Error("Missing metadata.name");
    }

    if (!manifest.apiVersion) {
      throw new Error("Missing apiVersion");
    }

    if (!manifest.apiVersion.startsWith("smarter.sh/v")) {
      throw new Error("Invalid apiVersion");
    }

    if (!manifest.kind) {
      throw new Error("Missing kind");
    }
  }

  async function parseManifest(file: File): Promise<Manifest> {
    const content = await file.text();
    const obj = load(content);
    return JSON.parse(JSON.stringify(obj)) as Manifest;
  }

  async function applyManifest(manifest: Manifest) {
    const response = await fetchDjangoUrl(sessionContext, sessionContext.ApiUrl, JSON.stringify(manifest));

    const data = await response.json();

    if (!response.ok) {
      throw new Error(`Manifest apply failed (${response.status})`);
    }

    return data;
  }

  async function processFile(file: File) {
    setIsUploading(true);

    try {
      const manifest = await parseManifest(file);

      validateManifest(manifest);
      const result = await applyManifest(manifest);

      showModal("Manifest Applied", result, false);
    } catch (error) {
      console.error(loggerPrefix, error);

      showModal("Manifest Apply Failed", error instanceof Error ? error.message : "Unknown error", true);
    } finally {
      setIsUploading(false);
    }
  }

  const handleFileSelected = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];

    try {
      if (file) await processFile(file);
    } finally {
      event.target.value = "";
    }
  };

  const handleDrop = async (event: React.DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    setIsDragActive(false);

    const file = event.dataTransfer.files?.[0];

    try {
      if (file) await processFile(file);
    } catch (error) {
      showModal("Error", error instanceof Error ? error.message : "Failed", true);
    }
  };

  return (
    <section id="manifest-apply">
      <div className="app-content flex-column-fluid">
        <div className="app-container container-xxl">
          <h3 className="pt-5">Apply Smarter YAML Manifest</h3>

          <input ref={fileInputRef} type="file" accept=".yaml,.yml" hidden onChange={handleFileSelected} />

          <button type="button" className="btn btn-sm btn-primary" disabled={isUploading} onClick={openFileDialog}>
            {isUploading ? "Uploading..." : "File Open"}
          </button>

          <div
            className={`manifest-drop-zone d-flex justify-content-center align-items-center ${
              isDragActive ? "drop-zone--hover" : ""
            }`}
            onDragOver={(e) => e.preventDefault()}
            onDrop={handleDrop}
          >
            <span>{isUploading ? "Uploading..." : "Drop Zone"}</span>
          </div>

          <DropZoneModal
            {...modal}
            onClose={() => setModal((m) => ({ ...m, open: false }))}
          />
        </div>
      </div>
    </section>
  );
}