MDK Logo

Dialog Components

Modal dialogs, confirmation prompts, and dialog utilities

Modal and dialog components for user interactions.

Prerequisites

  • Complete the installation
  • Import styles: import '@tetherto/mdk-react-devkit/styles.css'

Components

@tetherto/mdk-react-devkit

Add a spare part

import { AddSparePartModal } from '@tetherto/mdk-react-devkit'

Modal for registering a new spare part. Presents a part-type tab strip (Controller, PSU, Hashboard, …) and a form for miner model, part model, serial number, MAC address, status, location, tags, and a comment. Validation is controller-aware — the MAC address field appears and is required only when a controller part type is selected; other part types require a serial number.

When to use

Use this in an inventory "Spare Parts" view when an operator needs to register a single new part. Pair it with SparePartSubTypesModal (via the subTypes* props) so the user can manage the allowed part models without leaving the dialog.

Notes

  • Select fields default to "" (empty), which renders the placeholder. Switching part type clears the part model and any serial/MAC validation errors.
  • MAC validation uses the format 00:1A:2B:3C:4D:5E (case-insensitive, : or - separators).

Example

import { useState } from "react";

import {
  AddSparePartModal,
  Button,
  SPARE_PART_LOCATION_LABELS,
  SPARE_PART_LOCATIONS,
  SPARE_PART_STATUS_NAMES,
  SPARE_PART_STATUSES,
  SparePartNames,
  SparePartTypes,
} from "@tetherto/mdk-react-devkit";

const PART_TYPES = Object.entries(SparePartTypes).map(([, value]) => ({
  value,
  label: SparePartNames[value as keyof typeof SparePartNames] ?? value,
}));

const MINER_MODEL_OPTIONS = [
  { value: "antminer-s19", label: "Antminer S19" },
  { value: "antminer-s19j", label: "Antminer S19j" },
  { value: "whatsminer-m30s", label: "Whatsminer M30S" },
];

const MODEL_OPTIONS_MAP: Record<string, Array<{ value: string; label: string }>> = {
  [SparePartTypes.CONTROLLER]: [
    { value: "CT-S19", label: "CT-S19" },
    { value: "CT-S19j", label: "CT-S19j" },
  ],
  [SparePartTypes.PSU]: [{ value: "PSU-3000W", label: "PSU-3000W" }],
  [SparePartTypes.HASHBOARD]: [
    { value: "HB-S19", label: "HB-S19" },
    { value: "HB-S19j", label: "HB-S19j" },
  ],
};

const STATUS_OPTIONS = Object.values(SPARE_PART_STATUSES).map((value) => ({
  value,
  label: SPARE_PART_STATUS_NAMES[value as keyof typeof SPARE_PART_STATUS_NAMES] ?? value,
}));

const LOCATION_OPTIONS = Object.values(SPARE_PART_LOCATIONS).map((value) => ({
  value,
  label: SPARE_PART_LOCATION_LABELS[value] ?? value,
}));

export const AddSparePartModalExample = () => {
  const [isOpen, setIsOpen] = useState(false);
  const [activePartType, setActivePartType] = useState(PART_TYPES[0]?.value ?? "");

  const modelOptions = MODEL_OPTIONS_MAP[activePartType] ?? [];
  const isController = activePartType === SparePartTypes.CONTROLLER;

  return (
    <div>
      <Button variant="primary" onClick={() => setIsOpen(true)}>
        Register Part
      </Button>
      <AddSparePartModal
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        partTypes={PART_TYPES}
        defaultPartTypeId={activePartType}
        modelOptions={modelOptions}
        minerModelOptions={MINER_MODEL_OPTIONS}
        statusOptions={STATUS_OPTIONS}
        locationOptions={LOCATION_OPTIONS}
        isControllerPartTypeSelected={isController}
        onPartTypeChange={(id) => setActivePartType(id)}
        onSubmit={async () => {
          setIsOpen(false);
        }}
      />
    </div>
  );
};
@tetherto/mdk-react-devkit

Manage spare part subtypes

import { SparePartSubTypesModal } from '@tetherto/mdk-react-devkit'

Modal for viewing and adding spare part subtypes (part models) per part type. Presents a part-type tab strip, a table of existing subtypes for the active type, and an inline add form.

When to use

Use this to manage the list of allowed part models for each part type. It can be opened standalone or embedded from AddSparePartModal's "View Subtypes" button so users can add a missing model without losing their in-progress form.

Notes

  • The active part type is controlled by the parent: change activePartTypeId and supply the matching subTypes in onPartTypeChange.
  • The add form validates a non-empty name and clears on successful add.

Example

import { useState } from "react";

import { Button, SparePartSubTypesModal } from "@tetherto/mdk-react-devkit";

const PART_TYPES = [
  { value: "inventory-miner_part-controller", label: "Controller" },
  { value: "inventory-miner_part-psu", label: "PSU" },
  { value: "inventory-miner_part-hashboard", label: "Hashboard" },
];

const INITIAL_SUB_TYPES: Record<string, string[]> = {
  "inventory-miner_part-controller": ["CT-S19", "CT-S19j"],
  "inventory-miner_part-psu": ["PSU-3000W"],
  "inventory-miner_part-hashboard": ["HB-S19", "HB-S19j", "HB-S19XP"],
};

export const SparePartSubTypesModalExample = () => {
  const [isOpen, setIsOpen] = useState(false);
  const [activeId, setActiveId] = useState(PART_TYPES[0]?.value ?? "");
  const [subTypesMap, setSubTypesMap] = useState(INITIAL_SUB_TYPES);

  const handleAddSubType = async (name: string): Promise<{ error: string } | void> => {
    const existing = subTypesMap[activeId] ?? [];
    if (existing.includes(name)) return { error: "Subtype already exists" };
    setSubTypesMap((prev) => ({ ...prev, [activeId]: [...existing, name] }));
  };

  return (
    <div>
      <Button variant="outline" onClick={() => setIsOpen(true)}>
        View Subtypes
      </Button>
      <SparePartSubTypesModal
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        partTypes={PART_TYPES}
        activePartTypeId={activeId}
        onPartTypeChange={setActiveId}
        subTypes={subTypesMap[activeId] ?? []}
        onAddSubType={handleAddSubType}
      />
    </div>
  );
};
@tetherto/mdk-react-devkit

Bulk-add spare parts

import { BulkAddSparePartsModal } from '@tetherto/mdk-react-devkit'

Modal for bulk-adding spare parts from a CSV file. Provides a CSV template download, file selection with client-side parsing, and submits the parsed records. CSV parsing and validation helpers (parseCsvText, validateCSVRecords, mapRawRowToRecord, downloadCsvTemplate, CSVRecord) are exported alongside the component for use in the consuming submit handler.

When to use

Use this when operators need to register many spare parts at once. Validate the parsed records in your onSubmit with validateCSVRecords (location/status/model checks, duplicate detection) and return an { error } to surface a message in the modal.

CSV format

Template headers (downloadable from the modal): part, model, miner model, serial num, mac, status, location, comment. Max MAX_CSV_ITEMS (50) rows per upload. validateCSVRecords enforces valid part types, miner models, statuses, locations, per-part-type model subtypes, and duplicate serial/MAC detection (the latter only for controllers).

Example

import { useState } from "react";

import { BulkAddSparePartsModal, Button } from "@tetherto/mdk-react-devkit";

export const BulkAddSparePartsModalExample = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      <Button variant="primary" onClick={() => setIsOpen(true)}>
        Bulk Add Parts
      </Button>
      <BulkAddSparePartsModal
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        onSubmit={async () => {
          setIsOpen(false);
        }}
      />
    </div>
  );
};
@tetherto/mdk-react-devkit

Move a spare part

import { MoveSparePartModal } from '@tetherto/mdk-react-devkit'

Two-step modal for moving a single spare part. Step one shows the part details (SparePartDetails) alongside its current location and status, and lets the user pick a new location, status, and an observation. Step two previews the before → after transition with color-coded badges for confirmation before submitting.

When to use

Use this from a spare-parts inventory row action when an operator moves one part and you want an explicit confirm step showing exactly what will change. For moving many parts at once, use BatchMoveSparePartsModal instead.

Data shape

const sparePart: MoveSparePartModalSparePart = {
  id: 'sp-001',
  code: 'CB-AM-CB5_V10-01',  // shown via SparePartDetails
  type: 'CB5_V10',
  site: 'Site A',
  serialNum: 'test-miner',
  macAddress: 'aa:bb:cc:dd:ee:ff',
  location: 'site.warehouse',  // dot-separated location key
  status: 'ok_repaired',       // spare part status key
}

Label & color resolution

  • Location/status labels are resolved from the passed option lists (getOptionLabel).
  • The current/new badges are colored from SPARE_PART_LOCATION_BG_COLORS / SPARE_PART_STATUS_BG_COLORS; an unknown location key renders with no background.
  • The footer shows "No Changes made" until the target location or status differs from the current values, at which point "Save Changes" advances to the confirmation step.

Example

import { useState } from "react";

import {
  Button,
  MoveSparePartModal,
  SPARE_PART_LOCATION_LABELS,
  SPARE_PART_LOCATIONS,
  SPARE_PART_STATUS_NAMES,
  SPARE_PART_STATUSES,
} from "@tetherto/mdk-react-devkit";

const locationOptions = Object.values(SPARE_PART_LOCATIONS)
  .filter((value) => value !== SPARE_PART_LOCATIONS.SITE_CONTAINER)
  .map((value) => ({ value, label: SPARE_PART_LOCATION_LABELS[value] ?? value }));

const statusOptions = Object.values(SPARE_PART_STATUSES).map((value) => ({
  value,
  label: SPARE_PART_STATUS_NAMES[value as keyof typeof SPARE_PART_STATUS_NAMES] ?? value,
}));

const mockSparePart = {
  id: "sp-001",
  code: "HB-A001",
  type: "HB-S19",
  site: "Site A",
  serialNum: "SN123456",
  location: SPARE_PART_LOCATIONS.SITE_WAREHOUSE,
  status: SPARE_PART_STATUSES.OK_BRAND_NEW,
};

export const MoveSparePartModalExample = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      <Button variant="outline" onClick={() => setIsOpen(true)}>
        Move Spare Part
      </Button>
      <MoveSparePartModal
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        sparePart={mockSparePart}
        locationOptions={locationOptions}
        statusOptions={statusOptions}
        onSubmit={async () => {
          setIsOpen(false);
        }}
      />
    </div>
  );
};
@tetherto/mdk-react-devkit

Batch-move spare parts

import { BatchMoveSparePartsModal } from '@tetherto/mdk-react-devkit'

Modal for moving multiple spare parts at once. Shows the selected parts in a table (code, current location, current status) and lets the user choose a new location and/or status plus an observation, applied to every selected part in one submit.

When to use

Use this when an operator multi-selects spare-part rows and wants to relocate or re-status them together. At least one of location or status must be chosen. For a single part with a confirm step, use MoveSparePartModal.

Data shape

const spareParts: BatchMoveSparePart[] = [
  { id: 'sp-001', code: 'HB-A001', location: 'site.warehouse', status: 'ok_brand_new' },
  { id: 'sp-002', code: 'HB-A002', location: 'workshop.lab',   status: 'faulty' },
]

Notes

  • The form validates that either a location or a status is selected before submit.
  • Table location/status cells are resolved to display labels from the passed option lists.

Example

import { useState } from "react";

import {
  BatchMoveSparePartsModal,
  Button,
  SPARE_PART_LOCATION_LABELS,
  SPARE_PART_LOCATIONS,
  SPARE_PART_STATUS_NAMES,
  SPARE_PART_STATUSES,
} from "@tetherto/mdk-react-devkit";

const locationOptions = Object.values(SPARE_PART_LOCATIONS)
  .filter((value) => value !== SPARE_PART_LOCATIONS.SITE_CONTAINER)
  .map((value) => ({ value, label: SPARE_PART_LOCATION_LABELS[value] ?? value }));

const statusOptions = Object.values(SPARE_PART_STATUSES).map((value) => ({
  value,
  label: SPARE_PART_STATUS_NAMES[value as keyof typeof SPARE_PART_STATUS_NAMES] ?? value,
}));

const mockSpareParts = [
  { id: "sp-001", code: "HB-A001", location: SPARE_PART_LOCATIONS.SITE_WAREHOUSE, status: SPARE_PART_STATUSES.OK_BRAND_NEW },
  { id: "sp-002", code: "HB-A002", location: SPARE_PART_LOCATIONS.WORKSHOP_LAB, status: SPARE_PART_STATUSES.FAULTY },
  { id: "sp-003", code: "CB-B001", location: SPARE_PART_LOCATIONS.SITE_WAREHOUSE, status: SPARE_PART_STATUSES.OK_RECOVERED },
];

export const BatchMoveSparePartsModalExample = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      <Button variant="outline" onClick={() => setIsOpen(true)}>
        Batch Move ({mockSpareParts.length} parts)
      </Button>
      <BatchMoveSparePartsModal
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        spareParts={mockSpareParts}
        locationOptions={locationOptions}
        statusOptions={statusOptions}
        onSubmit={async () => {
          setIsOpen(false);
        }}
      />
    </div>
  );
};
@tetherto/mdk-react-devkit

View a device movement

import { MovementDetailsModal } from '@tetherto/mdk-react-devkit'

Modal that shows the details of a single historical device movement: a device summary (code, model, site, container, serial number, MAC) and the origin → destination transition of both location and status, with color-coded badges.

When to use

Use this in an inventory "Historical Movements" view when a user selects a movement row and you want to show the full before/after detail of where a device moved and how its status changed.

Data shape

const movement: MovementData = {
  origin: 'site.warehouse',          // dot-separated location key
  destination: 'workshop.lab',
  previousStatus: 'ok_brand_new',    // miner status key
  newStatus: 'faulty',
  device: {                          // raw device record (miner / spare part)
    code: 'M-123',
    tags: ['code-M-123'],
    type: 'antminer',
    info: { site: 'Site A', container: 'C1', serialNum: 'SN-9', macAddress: 'AA:BB' },
  },
  comments: 'Moved for repair',      // optional ReactNode
}

Label & color resolution

The component renders data only — all resolution happens in buildMovementDetailsViewModel:

  • Location labels come from getLocationLabel ('site.warehouse'Site Warehouse).
  • Location/status badge colors come from MINER_LOCATION_* / MINER_STATUS_* constants, falling back to a neutral border when a key is unknown.
  • Status labels come from MINER_STATUS_NAMES ('ok_brand_new'Brand New).
  • The device code is resolved with getMinerShortCode, and model falls back from info.subTypetype-.

Example

/**
 * Runnable example for MovementDetailsModal.
 */
import { type MovementData, MovementDetailsModal } from '@tetherto/mdk-react-devkit'

const exampleMovement: MovementData = {
  origin: 'site.warehouse',
  destination: 'workshop.lab',
  previousStatus: 'ok_brand_new',
  newStatus: 'faulty',
  device: {
    code: 'M-1042',
    tags: ['code-M-1042'],
    type: 'antminer',
    info: {
      site: 'Site A',
      container: 'C-12',
      serialNum: 'SN-9981',
      macAddress: 'AA:BB:CC:DD:EE:FF',
    },
  },
  comments: 'Moved to workshop lab for diagnostics.',
}

export const MovementDetailsModalExample = () => (
  <div className="mdk-example-row">
    <MovementDetailsModal
      isOpen
      movement={exampleMovement}
      onClose={() => {
        console.warn('modal closed')
      }}
    />
  </div>
)
@tetherto/mdk-react-devkit

Delete a spare part

import { ConfirmDeleteSparePartModal } from '@tetherto/mdk-react-devkit'

Confirmation modal for deleting a spare part. Warns that the action is irreversible and surfaces the part code so the user can verify before confirming.

When to use

Use this as the confirm step for a destructive "Delete" row action in a spare-parts inventory view.

Example

import { useState } from "react";

import { Button, ConfirmDeleteSparePartModal } from "@tetherto/mdk-react-devkit";

export const ConfirmDeleteSparePartModalExample = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      <Button variant="danger" onClick={() => setIsOpen(true)}>
        Delete Spare Part
      </Button>
      <ConfirmDeleteSparePartModal
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        onConfirm={async () => setIsOpen(false)}
        sparePart={{ id: "sp-001", code: "HB-A001" }}
      />
    </div>
  );
};
@tetherto/mdk-react-devkit

Import the public APIs on this page from @tetherto/mdk-react-devkit.

AddSparePartModal

Modal for registering a new spare part. Presents a part-type tab strip and a form for miner model, part model, serial number, MAC address (controllers only), status, location, tags, and a comment. Validation is controller-aware: controllers require a MAC address, other parts require a serial number. Receives option lists and the submit handler as props.

agent-ready

Props

PropTypeRequiredDefaultDescription
isOpenboolean--
onCloseVoidFunction--
partTypesSparePartSubTypesModalPartType[]--
defaultPartTypeIdstring | undefined--
modelOptionsFormSelectOption[]--
isModelOptionsLoadingboolean | undefined--
minerModelOptionsFormSelectOption[]--
statusOptionsFormSelectOption[]--
locationOptionsFormSelectOption[]--
isControllerPartTypeSelectedboolean | undefined--
onPartTypeChange(partTypeId: string) => void--
onSubmit(values: AddSparePartFormValues) => Promise<void | { fieldErrors?: { field: string; message: string; }[] | undefined; }>--
isLoadingboolean | undefined--
subTypesPartTypesSparePartSubTypesModalPartType[] | undefined--
subTypesActivePartTypeIdstring | undefined--
subTypesstring[] | undefined--
onSubTypesPartTypeChange((id: string) => void) | undefined--
onAddSubType((name: string) => Promise<void | { error?: string | undefined; }>) | undefined--
isSubTypesLoadingboolean | undefined--

AlertDialogAction

Primary confirmation button inside an <AlertDialog>; clicking dismisses the dialog and runs the action handler.

agent-ready

AlertDialogCancel

Secondary dismiss button inside an <AlertDialog>; closes the dialog without invoking the destructive action.

agent-ready

AlertDialogContent

Modal content surface for an <AlertDialog> — renders the centered panel above the overlay with focus trap.

agent-ready

AlertDialogDescription

Supporting body text inside an <AlertDialog>; conveys the consequences of the action being confirmed.

agent-ready

AlertDialogFooter

Right-aligned action row inside an <AlertDialog> — hosts the cancel and confirm buttons.

agent-ready

AlertDialogHeader

Top section of an <AlertDialog> that groups the title and description above the action row.

agent-ready

AlertDialogOverlay

Full-viewport scrim rendered behind an <AlertDialog> to block interaction with the page.

agent-ready

AlertDialogTitle

Prominent title text inside an <AlertDialog> summarising the action that requires confirmation.

agent-ready

AssignPoolModal

Modal dialog for bulk-assigning a set of selected miners to a pool config. Displays the miner list, a pool selector with metadata (unit/miner counts, last-updated time), an endpoints preview, and an optional credential template preview. Submission is async; the modal stays open with a loading state until the parent resolves.

agent-ready

Props

PropTypeRequiredDefaultDescription
isOpenboolean--
onClose() => void--
onSubmit(values: { pool: PoolSummary; }) => Promise<void>--
minersDevice[]--
poolConfigPoolConfigEntry[]--

BatchMoveSparePartsModal

Modal for moving multiple spare parts at once. Shows the selected parts in a table and lets the user choose a new location and/or status plus an observation, applied to every part. Receives the parts, option lists, and submit handler as props.

agent-ready

Props

PropTypeRequiredDefaultDescription
isOpenboolean--
onCloseVoidFunction--
sparePartsBatchMoveSparePart[]--
locationOptionsFormSelectOption[]--
statusOptionsFormSelectOption[]--
onSubmit(values: { location: string | null; status: string | null; observation: string | null; }) => void | Promise<void>--

BulkAddSparePartsModal

Modal for bulk-adding spare parts from a CSV file. Provides a CSV template download, file selection with client-side parsing, and submits the parsed records. Receives the submit handler as a prop; CSV parsing and validation helpers are exported alongside the component.

agent-ready

Props

PropTypeRequiredDefaultDescription
isOpenboolean--
onCloseVoidFunction--
onSubmit(records: CSVRecord[]) => Promise<void | { error?: string | undefined; }>--
isLoadingboolean | undefined--

ConfirmDeleteSparePartModal

Confirmation modal for deleting a spare part. Warns that the action is irreversible and surfaces the part code so the user can verify before confirming. Receives the part and confirm handler as props.

agent-ready

Props

PropTypeRequiredDefaultDescription
isOpenboolean | undefined--
onCloseVoidFunction | undefined--
onConfirm((sparePart: ConfirmDeleteSparePartModalSparePart) => void | Promise<void>) | undefined--
sparePartConfirmDeleteSparePartModalSparePart | undefined--
isLoadingboolean | undefined--

DialogContent

Centered modal surface for a <Dialog> — renders above the overlay with focus trap and Escape-to-close.

agent-ready

Props

PropTypeRequiredDefaultDescription
titlestring | undefined--
descriptionstring | undefined--
closeOnClickOutsideboolean | undefined--
closeOnEscapeboolean | undefined--
bareboolean | undefined--
closableboolean | undefined--
onCloseVoidFunction | undefined--

DialogDescription

Supporting body copy inside a <Dialog> rendered below the title.

agent-ready

DialogFooter

Action row at the bottom of a <Dialog> — typically primary/secondary buttons.

agent-ready

DialogHeader

Top region of a <Dialog> that groups the title and description.

agent-ready

Props

PropTypeRequiredDefaultDescription
bareboolean | undefined--
closableboolean | undefined--
onCloseVoidFunction | undefined--

DialogOverlay

Full-viewport scrim rendered behind an open <Dialog> to block background interaction.

agent-ready

DialogTitle

Prominent title text at the top of a <Dialog> summarising the modal's purpose.

agent-ready

MovementDetailsModal

Modal showing the details of a historical device movement — the device summary plus the origin → destination transition of location and status. Receives the selected movement as a prop — no internal data fetching.

agent-ready

Props

PropTypeRequiredDefaultDescription
isOpenboolean--
onClose() => void--
movementMovementData--

MoveSparePartModal

Two-step modal for moving a single spare part. Step one shows the part details with its current location and status and lets the user pick a new location, status, and observation; step two previews the before → after transition for confirmation. Receives the part, option lists, and submit handler as props.

agent-ready

Props

PropTypeRequiredDefaultDescription
isOpenboolean | undefined--
onCloseVoidFunction | undefined--
sparePartMoveSparePartModalSparePart | undefined--
requestedValues{ location?: string | undefined; status?: string | undefined; } | undefined--
locationOptionsFormSelectOption[]--
statusOptionsFormSelectOption[]--
onSubmit(values: { location: string; status: string; observation: string; }, sparePart: MoveSparePartModalSparePart) => void | Promise<void>--

SparePartSubTypesModal

Modal for viewing and adding spare part subtypes per part type. Presents a part-type tab strip, a table of existing subtypes for the active type, and an inline add form. Receives the active part type, subtype list, and add handler as props.

agent-ready

Props

PropTypeRequiredDefaultDescription
isOpenboolean--
onCloseVoidFunction--
partTypesSparePartSubTypesModalPartType[]--
activePartTypeIdstring--
onPartTypeChange(id: string) => void--
subTypesstring[]--
onAddSubType(name: string) => Promise<void | { error?: string | undefined; }>--
isLoadingboolean | undefined--

On this page