@supercycle/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +107 -0
  2. package/dist/cli.js +9 -0
  3. package/dist/commands/comments/delete.js +48 -0
  4. package/dist/commands/comments/index.js +3 -0
  5. package/dist/commands/conditions/index.js +3 -0
  6. package/dist/commands/conditions/list.js +20 -0
  7. package/dist/commands/custom-fields/definition.js +58 -0
  8. package/dist/commands/custom-fields/definitions.js +25 -0
  9. package/dist/commands/custom-fields/delete.js +45 -0
  10. package/dist/commands/custom-fields/index.js +3 -0
  11. package/dist/commands/custom-fields/set.js +43 -0
  12. package/dist/commands/custom-fields/update.js +19 -0
  13. package/dist/commands/cycles/comment.js +29 -0
  14. package/dist/commands/cycles/fulfil.js +24 -0
  15. package/dist/commands/cycles/get.js +50 -0
  16. package/dist/commands/cycles/index.js +3 -0
  17. package/dist/commands/cycles/list.js +124 -0
  18. package/dist/commands/cycles/overdue.js +25 -0
  19. package/dist/commands/cycles/pack.js +21 -0
  20. package/dist/commands/cycles/reassign.js +28 -0
  21. package/dist/commands/cycles/receive.js +23 -0
  22. package/dist/commands/cycles/reschedule.js +74 -0
  23. package/dist/commands/cycles/return.js +52 -0
  24. package/dist/commands/cycles/tag.js +45 -0
  25. package/dist/commands/cycles/to-fulfill.js +9 -0
  26. package/dist/commands/cycles/to-receive.js +9 -0
  27. package/dist/commands/cycles/today.js +18 -0
  28. package/dist/commands/items/availability.js +75 -0
  29. package/dist/commands/items/comment.js +29 -0
  30. package/dist/commands/items/create.js +57 -0
  31. package/dist/commands/items/get.js +50 -0
  32. package/dist/commands/items/index.js +3 -0
  33. package/dist/commands/items/list.js +25 -0
  34. package/dist/commands/items/update.js +60 -0
  35. package/dist/commands/locations/index.js +3 -0
  36. package/dist/commands/locations/list.js +19 -0
  37. package/dist/commands/login.js +76 -0
  38. package/dist/commands/products/import.js +65 -0
  39. package/dist/commands/products/index.js +3 -0
  40. package/dist/commands/products/list.js +19 -0
  41. package/dist/commands/returns/comment.js +29 -0
  42. package/dist/commands/returns/get.js +50 -0
  43. package/dist/commands/returns/index.js +3 -0
  44. package/dist/commands/returns/list.js +27 -0
  45. package/dist/commands/returns/update.js +68 -0
  46. package/dist/components/CommentResultView.js +40 -0
  47. package/dist/components/CustomFieldResultView.js +46 -0
  48. package/dist/components/CycleActionView.js +36 -0
  49. package/dist/components/CycleDetail.js +60 -0
  50. package/dist/components/CycleTable.js +68 -0
  51. package/dist/components/CyclesListView.js +30 -0
  52. package/dist/components/ErrorMessage.js +13 -0
  53. package/dist/components/ExitError.js +13 -0
  54. package/dist/components/ItemActionView.js +36 -0
  55. package/dist/components/ItemDetail.js +48 -0
  56. package/dist/components/ItemTable.js +58 -0
  57. package/dist/components/ItemsListView.js +30 -0
  58. package/dist/components/ItemsResultView.js +37 -0
  59. package/dist/components/JsonOutput.js +12 -0
  60. package/dist/components/Loading.js +11 -0
  61. package/dist/components/ProductTable.js +49 -0
  62. package/dist/components/ProductsListView.js +30 -0
  63. package/dist/components/ReferenceListView.js +49 -0
  64. package/dist/components/ReturnActionView.js +36 -0
  65. package/dist/components/ReturnDetail.js +55 -0
  66. package/dist/components/ReturnResultView.js +49 -0
  67. package/dist/components/ReturnTable.js +58 -0
  68. package/dist/components/ReturnsListView.js +30 -0
  69. package/dist/components/Status.js +33 -0
  70. package/dist/hooks/useAsync.js +32 -0
  71. package/dist/lib/client.js +302 -0
  72. package/dist/lib/config.js +38 -0
  73. package/dist/lib/format.js +49 -0
  74. package/dist/lib/listViewOptions.js +39 -0
  75. package/dist/lib/resolveClient.js +11 -0
  76. package/dist/lib/types.js +3 -0
  77. package/package.json +44 -0
@@ -0,0 +1,13 @@
1
+ import { useApp } from "ink";
2
+ import { useEffect } from "react";
3
+ // Write an error to stderr and exit non-zero without emitting anything to
4
+ // stdout — used in --json mode so piped output is never corrupted.
5
+ export default function ExitError({ message }) {
6
+ const { exit } = useApp();
7
+ useEffect(() => {
8
+ process.stderr.write(message + "\n");
9
+ process.exitCode = 1;
10
+ exit();
11
+ }, [message, exit]);
12
+ return null;
13
+ }
@@ -0,0 +1,36 @@
1
+ import { Box, Text } from "ink";
2
+ import React from "react";
3
+ import { useAsync } from "../hooks/useAsync.js";
4
+ import { resolveClient, NO_TOKEN_HINT } from "../lib/resolveClient.js";
5
+ import ErrorMessage from "./ErrorMessage.js";
6
+ import ExitError from "./ExitError.js";
7
+ import ItemDetail from "./ItemDetail.js";
8
+ import JsonOutput from "./JsonOutput.js";
9
+ import Loading from "./Loading.js";
10
+ // Runs a single item-mutating action, then shows a confirmation line plus the
11
+ // updated item (or raw JSON).
12
+ export default function ItemActionView({ action, successLabel, loadingLabel = "Updating item…", json, }) {
13
+ const resolved = resolveClient();
14
+ const state = useAsync(async () => {
15
+ if (!resolved.client)
16
+ throw new Error(resolved.error);
17
+ return action(resolved.client);
18
+ }, []);
19
+ if (state.loading) {
20
+ return json ? null : React.createElement(Loading, { label: loadingLabel });
21
+ }
22
+ if (state.error) {
23
+ if (json)
24
+ return React.createElement(ExitError, { message: state.error.message });
25
+ return (React.createElement(ErrorMessage, { message: state.error.message, hint: resolved.error ? NO_TOKEN_HINT : undefined }));
26
+ }
27
+ if (json) {
28
+ return React.createElement(JsonOutput, { data: state.data });
29
+ }
30
+ return (React.createElement(Box, { flexDirection: "column" },
31
+ React.createElement(Text, { color: "green" },
32
+ "\u2714 ",
33
+ successLabel(state.data)),
34
+ React.createElement(Box, { marginTop: 1 },
35
+ React.createElement(ItemDetail, { item: state.data }))));
36
+ }
@@ -0,0 +1,48 @@
1
+ import { Box, Text } from "ink";
2
+ import React from "react";
3
+ import { formatDateTime, titleCase, dash, EMPTY } from "../lib/format.js";
4
+ import Status from "./Status.js";
5
+ function Section({ title, children }) {
6
+ return (React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
7
+ React.createElement(Text, { bold: true, underline: true }, title),
8
+ children));
9
+ }
10
+ function Field({ label, children }) {
11
+ return (React.createElement(Box, null,
12
+ React.createElement(Box, { width: 18 },
13
+ React.createElement(Text, { dimColor: true }, label)),
14
+ React.createElement(Box, null, typeof children === "string" ? React.createElement(Text, null, children) : children)));
15
+ }
16
+ export default function ItemDetail({ item }) {
17
+ const { timelineEvents, tags, customFields } = item;
18
+ return (React.createElement(Box, { flexDirection: "column" },
19
+ React.createElement(Section, { title: `Item #${item.sequentialId ?? item.id}` },
20
+ React.createElement(Field, { label: "ID" }, String(item.id)),
21
+ React.createElement(Field, { label: "Product" }, dash(item.productTitle)),
22
+ React.createElement(Field, { label: "Variant" }, dash(item.variantTitle)),
23
+ React.createElement(Field, { label: "Serial" }, dash(item.serial)),
24
+ React.createElement(Field, { label: "Status" },
25
+ React.createElement(Status, { value: item.status })),
26
+ React.createElement(Field, { label: "Visibility" },
27
+ React.createElement(Status, { value: item.visibility })),
28
+ React.createElement(Field, { label: "Condition" }, dash(item.condition?.name ?? item.conditionId)),
29
+ React.createElement(Field, { label: "Pick location" }, dash(item.pickLocation))),
30
+ React.createElement(Section, { title: "Activity" },
31
+ React.createElement(Field, { label: "Active rental" }, dash(item.activeRentalId)),
32
+ React.createElement(Field, { label: "Active return" }, dash(item.activeReturnId))),
33
+ React.createElement(Section, { title: "Shopify" },
34
+ React.createElement(Field, { label: "Product ID" }, dash(item.shopifyProductId)),
35
+ React.createElement(Field, { label: "Variant ID" }, dash(item.shopifyVariantId)),
36
+ React.createElement(Field, { label: "Location ID" }, dash(item.locationShopifyId))),
37
+ React.createElement(Section, { title: "Dates" },
38
+ React.createElement(Field, { label: "Created" }, formatDateTime(item.createdAt))),
39
+ tags && tags.length > 0 ? (React.createElement(Section, { title: "Tags" },
40
+ React.createElement(Text, null, tags.join(", ")))) : null,
41
+ customFields && customFields.length > 0 ? (React.createElement(Section, { title: "Custom fields" }, customFields.map((field) => (React.createElement(Field, { key: field.id, label: field.key }, dash(field.value)))))) : null,
42
+ timelineEvents && timelineEvents.length > 0 ? (React.createElement(Section, { title: "Timeline" }, timelineEvents.map((event) => (React.createElement(Box, { key: event.id },
43
+ React.createElement(Box, { width: 20 },
44
+ React.createElement(Text, { dimColor: true, wrap: "truncate" }, formatDateTime(event.createdAt))),
45
+ React.createElement(Box, { width: 28 },
46
+ React.createElement(Text, { wrap: "truncate" }, titleCase(event.eventType))),
47
+ React.createElement(Text, { dimColor: true }, event.author ?? EMPTY)))))) : null));
48
+ }
@@ -0,0 +1,58 @@
1
+ import { Box, Text } from "ink";
2
+ import React from "react";
3
+ import { dash } from "../lib/format.js";
4
+ import Status from "./Status.js";
5
+ const COLUMNS = {
6
+ id: 8,
7
+ item: 7,
8
+ product: 24,
9
+ variant: 18,
10
+ status: 12,
11
+ visibility: 12,
12
+ active: 9,
13
+ };
14
+ function Cell({ width, noShrink, children, }) {
15
+ return (React.createElement(Box, { width: width, marginRight: 1, flexShrink: noShrink ? 0 : 1 }, children));
16
+ }
17
+ function HeaderCell({ width, label, noShrink, }) {
18
+ return (React.createElement(Cell, { width: width, noShrink: noShrink },
19
+ React.createElement(Text, { bold: true, dimColor: true, wrap: "truncate" }, label)));
20
+ }
21
+ export default function ItemTable({ items, nextPage }) {
22
+ if (items.length === 0) {
23
+ return React.createElement(Text, { dimColor: true }, "No items matched your filters.");
24
+ }
25
+ return (React.createElement(Box, { flexDirection: "column" },
26
+ React.createElement(Box, null,
27
+ React.createElement(HeaderCell, { width: COLUMNS.id, label: "ID", noShrink: true }),
28
+ React.createElement(HeaderCell, { width: COLUMNS.item, label: "Item #", noShrink: true }),
29
+ React.createElement(HeaderCell, { width: COLUMNS.product, label: "Product" }),
30
+ React.createElement(HeaderCell, { width: COLUMNS.variant, label: "Variant" }),
31
+ React.createElement(HeaderCell, { width: COLUMNS.status, label: "Status" }),
32
+ React.createElement(HeaderCell, { width: COLUMNS.visibility, label: "Visibility" }),
33
+ React.createElement(HeaderCell, { width: COLUMNS.active, label: "Rental" })),
34
+ items.map((item) => (React.createElement(Box, { key: item.id },
35
+ React.createElement(Cell, { width: COLUMNS.id, noShrink: true },
36
+ React.createElement(Text, { wrap: "truncate" }, item.id)),
37
+ React.createElement(Cell, { width: COLUMNS.item, noShrink: true },
38
+ React.createElement(Text, { dimColor: true, wrap: "truncate" }, item.sequentialId == null ? "—" : `#${item.sequentialId}`)),
39
+ React.createElement(Cell, { width: COLUMNS.product },
40
+ React.createElement(Text, { wrap: "truncate" }, dash(item.productTitle))),
41
+ React.createElement(Cell, { width: COLUMNS.variant },
42
+ React.createElement(Text, { wrap: "truncate" }, dash(item.variantTitle))),
43
+ React.createElement(Cell, { width: COLUMNS.status },
44
+ React.createElement(Status, { value: item.status })),
45
+ React.createElement(Cell, { width: COLUMNS.visibility },
46
+ React.createElement(Status, { value: item.visibility })),
47
+ React.createElement(Cell, { width: COLUMNS.active },
48
+ React.createElement(Text, { wrap: "truncate" }, dash(item.activeRentalId)))))),
49
+ React.createElement(Box, { marginTop: 1, flexDirection: "column" },
50
+ React.createElement(Text, { dimColor: true },
51
+ items.length,
52
+ " item",
53
+ items.length === 1 ? "" : "s",
54
+ " shown \u00B7 use the ID column with `items get <id>`"),
55
+ nextPage ? React.createElement(Text, { dimColor: true },
56
+ "Next page: --page ",
57
+ nextPage) : null)));
58
+ }
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+ import { useAsync } from "../hooks/useAsync.js";
3
+ import { resolveClient, NO_TOKEN_HINT } from "../lib/resolveClient.js";
4
+ import ErrorMessage from "./ErrorMessage.js";
5
+ import ExitError from "./ExitError.js";
6
+ import ItemTable from "./ItemTable.js";
7
+ import JsonOutput from "./JsonOutput.js";
8
+ import Loading from "./Loading.js";
9
+ // Fetch a list of items (optionally following every page) and render it as a
10
+ // table or raw JSON.
11
+ export default function ItemsListView({ filters, all, json }) {
12
+ const resolved = resolveClient();
13
+ const state = useAsync(async () => {
14
+ if (!resolved.client)
15
+ throw new Error(resolved.error);
16
+ return all ? resolved.client.listAllItems(filters) : resolved.client.listItems(filters);
17
+ }, []);
18
+ if (state.loading) {
19
+ return json ? null : React.createElement(Loading, { label: "Fetching items\u2026" });
20
+ }
21
+ if (state.error) {
22
+ if (json)
23
+ return React.createElement(ExitError, { message: state.error.message });
24
+ return (React.createElement(ErrorMessage, { message: state.error.message, hint: resolved.error ? NO_TOKEN_HINT : undefined }));
25
+ }
26
+ if (json) {
27
+ return React.createElement(JsonOutput, { data: state.data });
28
+ }
29
+ return React.createElement(ItemTable, { items: state.data.data, nextPage: state.data.nextPage });
30
+ }
@@ -0,0 +1,37 @@
1
+ import { Box, Text } from "ink";
2
+ import React from "react";
3
+ import { useAsync } from "../hooks/useAsync.js";
4
+ import { resolveClient, NO_TOKEN_HINT } from "../lib/resolveClient.js";
5
+ import ErrorMessage from "./ErrorMessage.js";
6
+ import ExitError from "./ExitError.js";
7
+ import ItemTable from "./ItemTable.js";
8
+ import JsonOutput from "./JsonOutput.js";
9
+ import Loading from "./Loading.js";
10
+ // Runs an action that returns a list of items (e.g. create) and renders a
11
+ // confirmation line plus the resulting items as a table.
12
+ export default function ItemsResultView({ action, successLabel, loadingLabel = "Saving…", json, }) {
13
+ const resolved = resolveClient();
14
+ const state = useAsync(async () => {
15
+ if (!resolved.client)
16
+ throw new Error(resolved.error);
17
+ return action(resolved.client);
18
+ }, []);
19
+ if (state.loading) {
20
+ return json ? null : React.createElement(Loading, { label: loadingLabel });
21
+ }
22
+ if (state.error) {
23
+ if (json)
24
+ return React.createElement(ExitError, { message: state.error.message });
25
+ return (React.createElement(ErrorMessage, { message: state.error.message, hint: resolved.error ? NO_TOKEN_HINT : undefined }));
26
+ }
27
+ if (json) {
28
+ return React.createElement(JsonOutput, { data: state.data });
29
+ }
30
+ const items = state.data;
31
+ return (React.createElement(Box, { flexDirection: "column" },
32
+ React.createElement(Text, { color: "green" },
33
+ "\u2714 ",
34
+ successLabel(items)),
35
+ React.createElement(Box, { marginTop: 1 },
36
+ React.createElement(ItemTable, { items: items, nextPage: null }))));
37
+ }
@@ -0,0 +1,12 @@
1
+ import { useApp } from "ink";
2
+ import { useEffect } from "react";
3
+ // Write raw JSON straight to stdout (bypassing Ink's layout so the output
4
+ // stays pipeable, e.g. into `jq`) and then exit.
5
+ export default function JsonOutput({ data }) {
6
+ const { exit } = useApp();
7
+ useEffect(() => {
8
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
9
+ exit();
10
+ }, [data, exit]);
11
+ return null;
12
+ }
@@ -0,0 +1,11 @@
1
+ import { Box, Text } from "ink";
2
+ import Spinner from "ink-spinner";
3
+ import React from "react";
4
+ export default function Loading({ label = "Loading…" }) {
5
+ return (React.createElement(Box, null,
6
+ React.createElement(Text, { color: "cyan" },
7
+ React.createElement(Spinner, { type: "dots" })),
8
+ React.createElement(Text, null,
9
+ " ",
10
+ label)));
11
+ }
@@ -0,0 +1,49 @@
1
+ import { Box, Text } from "ink";
2
+ import React from "react";
3
+ import { dash } from "../lib/format.js";
4
+ const COLUMNS = {
5
+ id: 8,
6
+ product: 8,
7
+ title: 30,
8
+ handle: 24,
9
+ variants: 9,
10
+ };
11
+ function Cell({ width, noShrink, children, }) {
12
+ return (React.createElement(Box, { width: width, marginRight: 1, flexShrink: noShrink ? 0 : 1 }, children));
13
+ }
14
+ function HeaderCell({ width, label, noShrink, }) {
15
+ return (React.createElement(Cell, { width: width, noShrink: noShrink },
16
+ React.createElement(Text, { bold: true, dimColor: true, wrap: "truncate" }, label)));
17
+ }
18
+ export default function ProductTable({ products, nextPage }) {
19
+ if (products.length === 0) {
20
+ return React.createElement(Text, { dimColor: true }, "No products matched your filters.");
21
+ }
22
+ return (React.createElement(Box, { flexDirection: "column" },
23
+ React.createElement(Box, null,
24
+ React.createElement(HeaderCell, { width: COLUMNS.id, label: "ID", noShrink: true }),
25
+ React.createElement(HeaderCell, { width: COLUMNS.product, label: "Prod #", noShrink: true }),
26
+ React.createElement(HeaderCell, { width: COLUMNS.title, label: "Title" }),
27
+ React.createElement(HeaderCell, { width: COLUMNS.handle, label: "Handle" }),
28
+ React.createElement(HeaderCell, { width: COLUMNS.variants, label: "Variants" })),
29
+ products.map((product) => (React.createElement(Box, { key: product.id },
30
+ React.createElement(Cell, { width: COLUMNS.id, noShrink: true },
31
+ React.createElement(Text, { wrap: "truncate" }, product.id)),
32
+ React.createElement(Cell, { width: COLUMNS.product, noShrink: true },
33
+ React.createElement(Text, { dimColor: true, wrap: "truncate" }, product.sequentialId == null ? "—" : `#${product.sequentialId}`)),
34
+ React.createElement(Cell, { width: COLUMNS.title },
35
+ React.createElement(Text, { wrap: "truncate" }, dash(product.title))),
36
+ React.createElement(Cell, { width: COLUMNS.handle },
37
+ React.createElement(Text, { dimColor: true, wrap: "truncate" }, dash(product.handle))),
38
+ React.createElement(Cell, { width: COLUMNS.variants },
39
+ React.createElement(Text, { wrap: "truncate" }, product.variants?.length ?? 0))))),
40
+ React.createElement(Box, { marginTop: 1, flexDirection: "column" },
41
+ React.createElement(Text, { dimColor: true },
42
+ products.length,
43
+ " product",
44
+ products.length === 1 ? "" : "s",
45
+ " shown"),
46
+ nextPage ? React.createElement(Text, { dimColor: true },
47
+ "Next page: --page ",
48
+ nextPage) : null)));
49
+ }
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+ import { useAsync } from "../hooks/useAsync.js";
3
+ import { resolveClient, NO_TOKEN_HINT } from "../lib/resolveClient.js";
4
+ import ErrorMessage from "./ErrorMessage.js";
5
+ import ExitError from "./ExitError.js";
6
+ import JsonOutput from "./JsonOutput.js";
7
+ import Loading from "./Loading.js";
8
+ import ProductTable from "./ProductTable.js";
9
+ // Fetch a list of products (optionally following every page) and render it as a
10
+ // table or raw JSON.
11
+ export default function ProductsListView({ filters, all, json }) {
12
+ const resolved = resolveClient();
13
+ const state = useAsync(async () => {
14
+ if (!resolved.client)
15
+ throw new Error(resolved.error);
16
+ return all ? resolved.client.listAllProducts(filters) : resolved.client.listProducts(filters);
17
+ }, []);
18
+ if (state.loading) {
19
+ return json ? null : React.createElement(Loading, { label: "Fetching products\u2026" });
20
+ }
21
+ if (state.error) {
22
+ if (json)
23
+ return React.createElement(ExitError, { message: state.error.message });
24
+ return (React.createElement(ErrorMessage, { message: state.error.message, hint: resolved.error ? NO_TOKEN_HINT : undefined }));
25
+ }
26
+ if (json) {
27
+ return React.createElement(JsonOutput, { data: state.data });
28
+ }
29
+ return React.createElement(ProductTable, { products: state.data.data, nextPage: state.data.nextPage });
30
+ }
@@ -0,0 +1,49 @@
1
+ import { Box, Text } from "ink";
2
+ import React from "react";
3
+ import { useAsync } from "../hooks/useAsync.js";
4
+ import { resolveClient, NO_TOKEN_HINT } from "../lib/resolveClient.js";
5
+ import ErrorMessage from "./ErrorMessage.js";
6
+ import ExitError from "./ExitError.js";
7
+ import JsonOutput from "./JsonOutput.js";
8
+ import Loading from "./Loading.js";
9
+ function HeaderCell({ width, label }) {
10
+ return (React.createElement(Box, { width: width, marginRight: 1, flexShrink: width ? 0 : 1 },
11
+ React.createElement(Text, { bold: true, dimColor: true, wrap: "truncate" }, label)));
12
+ }
13
+ // Fetch a small reference list and render it as a table (driven by the supplied
14
+ // columns) or raw JSON. Shared by the `locations list` and `conditions list`
15
+ // commands, whose response shapes differ.
16
+ export default function ReferenceListView({ fetch, columns, rowKey, loadingLabel, emptyLabel, noun, json, }) {
17
+ const resolved = resolveClient();
18
+ const state = useAsync(async () => {
19
+ if (!resolved.client)
20
+ throw new Error(resolved.error);
21
+ return fetch(resolved.client);
22
+ }, []);
23
+ if (state.loading) {
24
+ return json ? null : React.createElement(Loading, { label: loadingLabel });
25
+ }
26
+ if (state.error) {
27
+ if (json)
28
+ return React.createElement(ExitError, { message: state.error.message });
29
+ return (React.createElement(ErrorMessage, { message: state.error.message, hint: resolved.error ? NO_TOKEN_HINT : undefined }));
30
+ }
31
+ if (json) {
32
+ return React.createElement(JsonOutput, { data: state.data });
33
+ }
34
+ const rows = state.data.data;
35
+ if (rows.length === 0) {
36
+ return React.createElement(Text, { dimColor: true }, emptyLabel);
37
+ }
38
+ return (React.createElement(Box, { flexDirection: "column" },
39
+ React.createElement(Box, null, columns.map((column) => (React.createElement(HeaderCell, { key: column.label, width: column.width, label: column.label })))),
40
+ rows.map((row) => (React.createElement(Box, { key: rowKey(row) }, columns.map((column) => (React.createElement(Box, { key: column.label, width: column.width, marginRight: 1, flexShrink: column.width ? 0 : 1 },
41
+ React.createElement(Text, { wrap: "truncate" }, column.value(row)))))))),
42
+ React.createElement(Box, { marginTop: 1 },
43
+ React.createElement(Text, { dimColor: true },
44
+ rows.length,
45
+ " ",
46
+ noun,
47
+ rows.length === 1 ? "" : "s",
48
+ " shown"))));
49
+ }
@@ -0,0 +1,36 @@
1
+ import { Box, Text } from "ink";
2
+ import React from "react";
3
+ import { useAsync } from "../hooks/useAsync.js";
4
+ import { resolveClient, NO_TOKEN_HINT } from "../lib/resolveClient.js";
5
+ import ErrorMessage from "./ErrorMessage.js";
6
+ import ExitError from "./ExitError.js";
7
+ import JsonOutput from "./JsonOutput.js";
8
+ import Loading from "./Loading.js";
9
+ import ReturnDetail from "./ReturnDetail.js";
10
+ // Runs a single return-mutating action, then shows a confirmation line plus the
11
+ // updated return (or raw JSON).
12
+ export default function ReturnActionView({ action, successLabel, loadingLabel = "Updating return…", json, }) {
13
+ const resolved = resolveClient();
14
+ const state = useAsync(async () => {
15
+ if (!resolved.client)
16
+ throw new Error(resolved.error);
17
+ return action(resolved.client);
18
+ }, []);
19
+ if (state.loading) {
20
+ return json ? null : React.createElement(Loading, { label: loadingLabel });
21
+ }
22
+ if (state.error) {
23
+ if (json)
24
+ return React.createElement(ExitError, { message: state.error.message });
25
+ return (React.createElement(ErrorMessage, { message: state.error.message, hint: resolved.error ? NO_TOKEN_HINT : undefined }));
26
+ }
27
+ if (json) {
28
+ return React.createElement(JsonOutput, { data: state.data });
29
+ }
30
+ return (React.createElement(Box, { flexDirection: "column" },
31
+ React.createElement(Text, { color: "green" },
32
+ "\u2714 ",
33
+ successLabel(state.data)),
34
+ React.createElement(Box, { marginTop: 1 },
35
+ React.createElement(ReturnDetail, { order: state.data }))));
36
+ }
@@ -0,0 +1,55 @@
1
+ import { Box, Text } from "ink";
2
+ import React from "react";
3
+ import { customerName, formatDateTime, titleCase, dash, EMPTY } from "../lib/format.js";
4
+ import Status from "./Status.js";
5
+ function Section({ title, children }) {
6
+ return (React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
7
+ React.createElement(Text, { bold: true, underline: true }, title),
8
+ children));
9
+ }
10
+ function Field({ label, children }) {
11
+ return (React.createElement(Box, null,
12
+ React.createElement(Box, { width: 18 },
13
+ React.createElement(Text, { dimColor: true }, label)),
14
+ React.createElement(Box, null, typeof children === "string" ? React.createElement(Text, null, children) : children)));
15
+ }
16
+ export default function ReturnDetail({ order }) {
17
+ const { returnLines, timelineEvents, tags } = order;
18
+ return (React.createElement(Box, { flexDirection: "column" },
19
+ React.createElement(Section, { title: `Return #${order.sequentialId ?? order.id}` },
20
+ React.createElement(Field, { label: "ID" }, String(order.id)),
21
+ React.createElement(Field, { label: "Status" },
22
+ React.createElement(Status, { value: order.status })),
23
+ React.createElement(Field, { label: "Receival" },
24
+ React.createElement(Status, { value: order.receivalStatus })),
25
+ React.createElement(Field, { label: "Credit" },
26
+ React.createElement(Status, { value: order.creditStatus })),
27
+ React.createElement(Field, { label: "Requested" }, formatDateTime(order.requestedAt)),
28
+ React.createElement(Field, { label: "Created" }, formatDateTime(order.createdAt))),
29
+ React.createElement(Section, { title: "Customer" },
30
+ React.createElement(Field, { label: "Name" }, customerName(order.customer)),
31
+ React.createElement(Field, { label: "Email" }, dash(order.customer?.email))),
32
+ returnLines && returnLines.length > 0 ? (React.createElement(Section, { title: `Return lines (${returnLines.length})` },
33
+ React.createElement(Box, null,
34
+ React.createElement(Box, { width: 8, marginRight: 1 },
35
+ React.createElement(Text, { bold: true, dimColor: true }, "Cycle")),
36
+ React.createElement(Box, { width: 26, marginRight: 1 },
37
+ React.createElement(Text, { bold: true, dimColor: true }, "Product")),
38
+ React.createElement(Box, { width: 12 },
39
+ React.createElement(Text, { bold: true, dimColor: true }, "Status"))),
40
+ returnLines.map((line) => (React.createElement(Box, { key: line.id },
41
+ React.createElement(Box, { width: 8, marginRight: 1 },
42
+ React.createElement(Text, { wrap: "truncate" }, line.rentalId)),
43
+ React.createElement(Box, { width: 26, marginRight: 1 },
44
+ React.createElement(Text, { wrap: "truncate" }, dash(line.item?.productTitle))),
45
+ React.createElement(Box, { width: 12 },
46
+ React.createElement(Status, { value: line.status }))))))) : null,
47
+ tags && tags.length > 0 ? (React.createElement(Section, { title: "Tags" },
48
+ React.createElement(Text, null, tags.join(", ")))) : null,
49
+ timelineEvents && timelineEvents.length > 0 ? (React.createElement(Section, { title: "Timeline" }, timelineEvents.map((event) => (React.createElement(Box, { key: event.id },
50
+ React.createElement(Box, { width: 20 },
51
+ React.createElement(Text, { dimColor: true, wrap: "truncate" }, formatDateTime(event.createdAt))),
52
+ React.createElement(Box, { width: 28 },
53
+ React.createElement(Text, { wrap: "truncate" }, titleCase(event.eventType))),
54
+ React.createElement(Text, { dimColor: true }, event.author ?? EMPTY)))))) : null));
55
+ }
@@ -0,0 +1,49 @@
1
+ import { Box, Text } from "ink";
2
+ import React from "react";
3
+ import { useAsync } from "../hooks/useAsync.js";
4
+ import { titleCase } from "../lib/format.js";
5
+ import { resolveClient, NO_TOKEN_HINT } from "../lib/resolveClient.js";
6
+ import ErrorMessage from "./ErrorMessage.js";
7
+ import ExitError from "./ExitError.js";
8
+ import JsonOutput from "./JsonOutput.js";
9
+ import Loading from "./Loading.js";
10
+ export default function ReturnResultView({ action, loadingLabel = "Creating return…", json, }) {
11
+ const resolved = resolveClient();
12
+ const state = useAsync(async () => {
13
+ if (!resolved.client)
14
+ throw new Error(resolved.error);
15
+ return action(resolved.client);
16
+ }, []);
17
+ if (state.loading) {
18
+ return json ? null : React.createElement(Loading, { label: loadingLabel });
19
+ }
20
+ if (state.error) {
21
+ if (json)
22
+ return React.createElement(ExitError, { message: state.error.message });
23
+ return (React.createElement(ErrorMessage, { message: state.error.message, hint: resolved.error ? NO_TOKEN_HINT : undefined }));
24
+ }
25
+ if (json) {
26
+ return React.createElement(JsonOutput, { data: state.data });
27
+ }
28
+ const { errors = [], returnOrders = [] } = state.data;
29
+ return (React.createElement(Box, { flexDirection: "column" },
30
+ errors.length > 0
31
+ ? errors.map((err, i) => (React.createElement(Text, { key: i, color: "red" },
32
+ "\u2716 ",
33
+ err)))
34
+ : null,
35
+ returnOrders.length > 0 ? (React.createElement(Box, { flexDirection: "column" },
36
+ React.createElement(Text, { color: "green" },
37
+ "\u2714 Created ",
38
+ returnOrders.length,
39
+ " return",
40
+ returnOrders.length === 1 ? "" : "s"),
41
+ returnOrders.map((ro) => (React.createElement(Text, { key: ro.id },
42
+ " ",
43
+ "Return #",
44
+ ro.sequentialId,
45
+ " (ID ",
46
+ ro.id,
47
+ ") \u2014 ",
48
+ titleCase(ro.status)))))) : errors.length === 0 ? (React.createElement(Text, { dimColor: true }, "No returns were created.")) : null));
49
+ }
@@ -0,0 +1,58 @@
1
+ import { Box, Text } from "ink";
2
+ import React from "react";
3
+ import { customerName } from "../lib/format.js";
4
+ import Status from "./Status.js";
5
+ const COLUMNS = {
6
+ id: 8,
7
+ ret: 8,
8
+ customer: 20,
9
+ status: 13,
10
+ receival: 12,
11
+ credit: 12,
12
+ lines: 6,
13
+ };
14
+ function Cell({ width, noShrink, children, }) {
15
+ return (React.createElement(Box, { width: width, marginRight: 1, flexShrink: noShrink ? 0 : 1 }, children));
16
+ }
17
+ function HeaderCell({ width, label, noShrink, }) {
18
+ return (React.createElement(Cell, { width: width, noShrink: noShrink },
19
+ React.createElement(Text, { bold: true, dimColor: true, wrap: "truncate" }, label)));
20
+ }
21
+ export default function ReturnTable({ returns, nextPage }) {
22
+ if (returns.length === 0) {
23
+ return React.createElement(Text, { dimColor: true }, "No returns matched your filters.");
24
+ }
25
+ return (React.createElement(Box, { flexDirection: "column" },
26
+ React.createElement(Box, null,
27
+ React.createElement(HeaderCell, { width: COLUMNS.id, label: "ID", noShrink: true }),
28
+ React.createElement(HeaderCell, { width: COLUMNS.ret, label: "Return #", noShrink: true }),
29
+ React.createElement(HeaderCell, { width: COLUMNS.customer, label: "Customer" }),
30
+ React.createElement(HeaderCell, { width: COLUMNS.status, label: "Status" }),
31
+ React.createElement(HeaderCell, { width: COLUMNS.receival, label: "Receival" }),
32
+ React.createElement(HeaderCell, { width: COLUMNS.credit, label: "Credit" }),
33
+ React.createElement(HeaderCell, { width: COLUMNS.lines, label: "Lines" })),
34
+ returns.map((ret) => (React.createElement(Box, { key: ret.id },
35
+ React.createElement(Cell, { width: COLUMNS.id, noShrink: true },
36
+ React.createElement(Text, { wrap: "truncate" }, ret.id)),
37
+ React.createElement(Cell, { width: COLUMNS.ret, noShrink: true },
38
+ React.createElement(Text, { dimColor: true, wrap: "truncate" }, ret.sequentialId == null ? "—" : `#${ret.sequentialId}`)),
39
+ React.createElement(Cell, { width: COLUMNS.customer },
40
+ React.createElement(Text, { wrap: "truncate" }, customerName(ret.customer))),
41
+ React.createElement(Cell, { width: COLUMNS.status },
42
+ React.createElement(Status, { value: ret.status })),
43
+ React.createElement(Cell, { width: COLUMNS.receival },
44
+ React.createElement(Status, { value: ret.receivalStatus })),
45
+ React.createElement(Cell, { width: COLUMNS.credit },
46
+ React.createElement(Status, { value: ret.creditStatus })),
47
+ React.createElement(Cell, { width: COLUMNS.lines },
48
+ React.createElement(Text, { wrap: "truncate" }, ret.returnLines?.length ?? 0))))),
49
+ React.createElement(Box, { marginTop: 1, flexDirection: "column" },
50
+ React.createElement(Text, { dimColor: true },
51
+ returns.length,
52
+ " return",
53
+ returns.length === 1 ? "" : "s",
54
+ " shown \u00B7 use the ID column with `returns get <id>`"),
55
+ nextPage ? React.createElement(Text, { dimColor: true },
56
+ "Next page: --page ",
57
+ nextPage) : null)));
58
+ }
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+ import { useAsync } from "../hooks/useAsync.js";
3
+ import { resolveClient, NO_TOKEN_HINT } from "../lib/resolveClient.js";
4
+ import ErrorMessage from "./ErrorMessage.js";
5
+ import ExitError from "./ExitError.js";
6
+ import JsonOutput from "./JsonOutput.js";
7
+ import Loading from "./Loading.js";
8
+ import ReturnTable from "./ReturnTable.js";
9
+ // Fetch a list of returns (optionally following every page) and render it as a
10
+ // table or raw JSON.
11
+ export default function ReturnsListView({ filters, all, json }) {
12
+ const resolved = resolveClient();
13
+ const state = useAsync(async () => {
14
+ if (!resolved.client)
15
+ throw new Error(resolved.error);
16
+ return all ? resolved.client.listAllReturns(filters) : resolved.client.listReturns(filters);
17
+ }, []);
18
+ if (state.loading) {
19
+ return json ? null : React.createElement(Loading, { label: "Fetching returns\u2026" });
20
+ }
21
+ if (state.error) {
22
+ if (json)
23
+ return React.createElement(ExitError, { message: state.error.message });
24
+ return (React.createElement(ErrorMessage, { message: state.error.message, hint: resolved.error ? NO_TOKEN_HINT : undefined }));
25
+ }
26
+ if (json) {
27
+ return React.createElement(JsonOutput, { data: state.data });
28
+ }
29
+ return React.createElement(ReturnTable, { returns: state.data.data, nextPage: state.data.nextPage });
30
+ }