mirror of
https://github.com/logos-storage/logos-storage-marketplace-ui.git
synced 2026-01-05 23:13:08 +00:00
Refactor storage request
This commit is contained in:
parent
655cbbbaa0
commit
932ca9d4e3
@ -67,6 +67,11 @@ export function AvailabilityCreate({ space }: Props) {
|
||||
if (step === components.length) {
|
||||
setAvailability(defaultAvailabilityData);
|
||||
|
||||
dispatch({
|
||||
step: 0,
|
||||
type: "next",
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: "close",
|
||||
});
|
||||
|
||||
@ -1,16 +1,22 @@
|
||||
import { Placeholder } from "@codex-storage/marketplace-ui-components";
|
||||
import { CircleCheck } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import "./StorageRequestDone.css";
|
||||
import { StorageRequestComponentProps } from "./types";
|
||||
import { useEffect } from "react";
|
||||
|
||||
type Props = {
|
||||
onChangeNextState: (value: "enable" | "disable") => void;
|
||||
};
|
||||
|
||||
export function StorageRequestDone({ onChangeNextState }: Props) {
|
||||
// TODO rename
|
||||
export function StorageRequestDone({ dispatch }: StorageRequestComponentProps) {
|
||||
useEffect(() => {
|
||||
onChangeNextState("enable");
|
||||
}, [onChangeNextState]);
|
||||
dispatch({
|
||||
type: "toggle-next",
|
||||
isNextEnable: true,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: "toggle-back",
|
||||
isBackEnable: false,
|
||||
});
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<Placeholder
|
||||
|
||||
27
src/components/StorageRequestSetup/StorageRequestError.tsx
Normal file
27
src/components/StorageRequestSetup/StorageRequestError.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { StorageRequestComponentProps } from "./types";
|
||||
import { useEffect } from "react";
|
||||
import { ErrorPlaceholder } from "../ErrorPlaceholder/ErrorPlaceholder";
|
||||
|
||||
export function StorageRequestError({
|
||||
dispatch,
|
||||
error,
|
||||
}: StorageRequestComponentProps) {
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: "toggle-next",
|
||||
isNextEnable: false,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: "toggle-back",
|
||||
isBackEnable: true,
|
||||
});
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<ErrorPlaceholder
|
||||
subtitle="Error when trying to create storage request."
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import { CodexSdk } from "../../sdk/codex";
|
||||
import "./StorageRequestFileChooser.css";
|
||||
import { ChangeEvent, useEffect, useRef, useState } from "react";
|
||||
import { ChangeEvent, useEffect } from "react";
|
||||
import { WebStorage } from "../../utils/web-storage";
|
||||
import { classnames } from "../../utils/classnames";
|
||||
import {
|
||||
@ -10,57 +10,47 @@ import {
|
||||
WebFileIcon,
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import { useData } from "../../hooks/useData";
|
||||
import { StorageRequestComponentProps } from "./types";
|
||||
|
||||
type Props = {
|
||||
onChangeNextState: (value: "enable" | "disable") => void;
|
||||
};
|
||||
|
||||
export function StorageRequestFileChooser({ onChangeNextState }: Props) {
|
||||
export function StorageRequestFileChooser({
|
||||
storageRequest,
|
||||
dispatch,
|
||||
onStorageRequestChange,
|
||||
}: StorageRequestComponentProps) {
|
||||
const files = useData();
|
||||
const [cid, setCid] = useState("");
|
||||
const cache = useRef("");
|
||||
|
||||
useEffect(() => {
|
||||
WebStorage.get<string>("storage-request-step-1").then((val) => {
|
||||
cache.current = val || "";
|
||||
|
||||
setCid(val || "");
|
||||
onChangeNextState(!val ? "disable" : "enable");
|
||||
dispatch({
|
||||
type: "toggle-next",
|
||||
isNextEnable: !!storageRequest.cid,
|
||||
});
|
||||
|
||||
return () => {
|
||||
WebStorage.set("storage-request-step-1", cache.current || "");
|
||||
};
|
||||
}, [onChangeNextState]);
|
||||
dispatch({
|
||||
type: "toggle-back",
|
||||
isBackEnable: true,
|
||||
});
|
||||
}, [dispatch, storageRequest]);
|
||||
|
||||
const onSelected = (o: DropdownOption) => {
|
||||
setCid(o.subtitle || "");
|
||||
onChangeNextState(!o.subtitle ? "disable" : "enable");
|
||||
cache.current = o.subtitle || "";
|
||||
onStorageRequestChange({ cid: o.subtitle });
|
||||
};
|
||||
|
||||
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setCid(e.currentTarget.value);
|
||||
onChangeNextState(!e.currentTarget.value ? "disable" : "enable");
|
||||
cache.current = e.currentTarget.value;
|
||||
const value = e.currentTarget.value;
|
||||
onStorageRequestChange({ cid: value });
|
||||
};
|
||||
|
||||
const onSuccess = (data: string, file: File) => {
|
||||
// TODO Move this to proxy object
|
||||
WebStorage.set(data, {
|
||||
type: file.type,
|
||||
name: file.name,
|
||||
});
|
||||
|
||||
onChangeNextState("enable");
|
||||
|
||||
setCid(data);
|
||||
cache.current = data;
|
||||
onStorageRequestChange({ cid: data });
|
||||
};
|
||||
|
||||
const onDelete = () => {
|
||||
setCid("");
|
||||
onChangeNextState("disable");
|
||||
};
|
||||
const onDelete = () => onStorageRequestChange({ cid: "" });
|
||||
|
||||
const options =
|
||||
files.map((f) => {
|
||||
@ -84,12 +74,12 @@ export function StorageRequestFileChooser({ onChangeNextState }: Props) {
|
||||
id="cid"
|
||||
placeholder="Select or type your CID"
|
||||
onChange={onChange}
|
||||
value={cid}
|
||||
value={storageRequest.cid}
|
||||
options={options}
|
||||
onSelected={onSelected}
|
||||
className={classnames(
|
||||
["storageRequestFileChooser-dropdown"],
|
||||
["storageRequestFileChooser-dropdown-success", !!cid]
|
||||
["storageRequestFileChooser-dropdown-success", !!storageRequest.cid]
|
||||
)}
|
||||
/>
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
border-radius: var(--codex-border-radius);
|
||||
background-color: rgb(56 56 56);
|
||||
align-items: center;
|
||||
padding: 1rem 2rem;
|
||||
padding: 1rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { WebStorage } from "../../utils/web-storage";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import "./StorageRequestReview.css";
|
||||
import { Alert } from "@codex-storage/marketplace-ui-components";
|
||||
import { CardNumbers } from "../CardNumbers/CardNumbers";
|
||||
import { FileWarning } from "lucide-react";
|
||||
import { classnames } from "../../utils/classnames";
|
||||
import { AvailabilityUnit, StorageRequestCriteria } from "./types";
|
||||
|
||||
type Props = {
|
||||
onChangeNextState: (value: "enable" | "disable") => void;
|
||||
};
|
||||
import {
|
||||
AvailabilityUnit,
|
||||
StorageRequest,
|
||||
StorageRequestComponentProps,
|
||||
} from "./types";
|
||||
|
||||
type Durability = {
|
||||
nodes: number;
|
||||
@ -35,84 +34,63 @@ const findDurabilityIndex = (d: Durability) => {
|
||||
|
||||
const units = ["days", "minutes", "hours", "days", "months", "years"];
|
||||
|
||||
export function StorageRequestReview({ onChangeNextState }: Props) {
|
||||
const [cid, setCid] = useState("");
|
||||
export function StorageRequestReview({
|
||||
dispatch,
|
||||
onStorageRequestChange,
|
||||
storageRequest,
|
||||
}: StorageRequestComponentProps) {
|
||||
const [durability, setDurability] = useState<number>(1);
|
||||
const [data, setData] = useState<StorageRequestCriteria>({
|
||||
availabilityUnit: "days",
|
||||
availability: 1,
|
||||
tolerance: 1,
|
||||
proofProbability: 1,
|
||||
nodes: 3,
|
||||
reward: 10,
|
||||
collateral: 10,
|
||||
expiration: 300,
|
||||
});
|
||||
|
||||
const isInvalidConstrainst = useCallback(
|
||||
(nodes: number, tolerance: number) => {
|
||||
const ecK = nodes - tolerance;
|
||||
const ecM = tolerance;
|
||||
|
||||
return ecK <= 1 || ecK < ecM;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
WebStorage.get<StorageRequestCriteria>("storage-request-criteria"),
|
||||
WebStorage.get<string>("storage-request-step-1"),
|
||||
]).then(([d, cid]) => {
|
||||
if (d) {
|
||||
setData(d);
|
||||
const invalid = isInvalidConstrainst(
|
||||
storageRequest.nodes,
|
||||
storageRequest.tolerance
|
||||
);
|
||||
|
||||
const index = findDurabilityIndex({
|
||||
nodes: d.nodes,
|
||||
tolerance: d.tolerance,
|
||||
proofProbability: d.proofProbability,
|
||||
});
|
||||
|
||||
setDurability(index + 1);
|
||||
} else {
|
||||
WebStorage.set("storage-request-criteria", {
|
||||
availabilityUnit: "days",
|
||||
availability: 1,
|
||||
tolerance: 1,
|
||||
proofProbability: 1,
|
||||
nodes: 3,
|
||||
reward: 10,
|
||||
collateral: 10,
|
||||
expiration: 300,
|
||||
});
|
||||
}
|
||||
|
||||
if (cid) {
|
||||
setCid(cid);
|
||||
}
|
||||
|
||||
onChangeNextState("enable");
|
||||
dispatch({
|
||||
type: "toggle-next",
|
||||
isNextEnable: !invalid,
|
||||
});
|
||||
}, [onChangeNextState]);
|
||||
|
||||
const updateData = (p: Partial<StorageRequestCriteria>) => {
|
||||
setData((d) => {
|
||||
const newData = { ...d, ...p };
|
||||
|
||||
WebStorage.set("storage-request-criteria", newData);
|
||||
|
||||
return newData;
|
||||
dispatch({
|
||||
type: "toggle-back",
|
||||
isBackEnable: true,
|
||||
});
|
||||
}, [dispatch, storageRequest]);
|
||||
|
||||
const onUpdateDurability = (data: Partial<StorageRequest>) => {
|
||||
onStorageRequestChange(data);
|
||||
|
||||
const index = findDurabilityIndex({
|
||||
nodes: storageRequest.nodes,
|
||||
tolerance: storageRequest.tolerance,
|
||||
proofProbability: storageRequest.proofProbability,
|
||||
});
|
||||
|
||||
setDurability(index + 1);
|
||||
};
|
||||
|
||||
const onDurabilityChange = (d: number) => {
|
||||
const durability = durabilities[d - 1];
|
||||
|
||||
if (durability) {
|
||||
updateData(durability);
|
||||
onStorageRequestChange(durability);
|
||||
setDurability(d);
|
||||
} else {
|
||||
setDurability(0);
|
||||
}
|
||||
};
|
||||
|
||||
const isInvalidConstrainst = (nodes: number, tolerance: number) => {
|
||||
const ecK = nodes - tolerance;
|
||||
const ecM = tolerance;
|
||||
|
||||
return ecK <= 1 || ecK < ecM;
|
||||
};
|
||||
|
||||
const isInvalidNodes = (nodes: string) => {
|
||||
const error = isInvalidNumber(nodes);
|
||||
|
||||
@ -122,7 +100,7 @@ export function StorageRequestReview({ onChangeNextState }: Props) {
|
||||
|
||||
const n = Number(nodes);
|
||||
|
||||
if (isInvalidConstrainst(n, data.tolerance)) {
|
||||
if (isInvalidConstrainst(n, storageRequest.tolerance)) {
|
||||
return "The data does not match Codex contrainst";
|
||||
}
|
||||
|
||||
@ -138,11 +116,11 @@ export function StorageRequestReview({ onChangeNextState }: Props) {
|
||||
|
||||
const n = Number(tolerance);
|
||||
|
||||
if (n > data.nodes) {
|
||||
if (n > storageRequest.nodes) {
|
||||
return "The tolerance cannot be greater than the nodes.";
|
||||
}
|
||||
|
||||
if (isInvalidConstrainst(data.nodes, n)) {
|
||||
if (isInvalidConstrainst(storageRequest.nodes, n)) {
|
||||
return "The data does not match Codex contrainst.";
|
||||
}
|
||||
|
||||
@ -172,47 +150,14 @@ export function StorageRequestReview({ onChangeNextState }: Props) {
|
||||
const isInvalidNumber = (value: string) =>
|
||||
isNaN(Number(value)) ? "The value is not a number" : "";
|
||||
|
||||
const onNodesChange = (value: string) => {
|
||||
const nodes = Number(value);
|
||||
const onNodesChange = (value: string) =>
|
||||
onUpdateDurability({ nodes: Number(value) });
|
||||
|
||||
updateData({ nodes });
|
||||
const onToleranceChange = (value: string) =>
|
||||
onUpdateDurability({ tolerance: Number(value) });
|
||||
|
||||
const index = findDurabilityIndex({
|
||||
nodes: nodes,
|
||||
tolerance: data.tolerance,
|
||||
proofProbability: data.proofProbability,
|
||||
});
|
||||
|
||||
setDurability(index + 1);
|
||||
};
|
||||
|
||||
const onToleranceChange = (value: string) => {
|
||||
const tolerance = Number(value);
|
||||
|
||||
updateData({ tolerance });
|
||||
|
||||
const index = findDurabilityIndex({
|
||||
nodes: data.nodes,
|
||||
tolerance: tolerance,
|
||||
proofProbability: data.proofProbability,
|
||||
});
|
||||
|
||||
setDurability(index + 1);
|
||||
};
|
||||
|
||||
const onProofProbabilityChange = (value: string) => {
|
||||
const proofProbability = Number(value);
|
||||
|
||||
updateData({ proofProbability });
|
||||
|
||||
const index = findDurabilityIndex({
|
||||
nodes: data.nodes,
|
||||
tolerance: data.tolerance,
|
||||
proofProbability: proofProbability,
|
||||
});
|
||||
|
||||
setDurability(index + 1);
|
||||
};
|
||||
const onProofProbabilityChange = (value: string) =>
|
||||
onUpdateDurability({ proofProbability: Number(value) });
|
||||
|
||||
const onAvailabilityChange = (value: string) => {
|
||||
const [availability, availabilityUnit = "days"] = value.split(" ");
|
||||
@ -221,29 +166,20 @@ export function StorageRequestReview({ onChangeNextState }: Props) {
|
||||
// availabilityUnit += "s";
|
||||
// }
|
||||
|
||||
updateData({
|
||||
onStorageRequestChange({
|
||||
availability: Number(availability),
|
||||
availabilityUnit: availabilityUnit as AvailabilityUnit,
|
||||
});
|
||||
};
|
||||
|
||||
const onRewardChange = (value: string) => {
|
||||
const reward = Number(value);
|
||||
const onRewardChange = (value: string) =>
|
||||
onStorageRequestChange({ reward: Number(value) });
|
||||
|
||||
updateData({ reward });
|
||||
};
|
||||
const onExpirationChange = (value: string) =>
|
||||
onStorageRequestChange({ expiration: Number(value) });
|
||||
|
||||
const onExpirationChange = (value: string) => {
|
||||
const expiration = Number(value);
|
||||
|
||||
updateData({ expiration });
|
||||
};
|
||||
|
||||
const onCollateralChange = (value: string) => {
|
||||
const collateral = Number(value);
|
||||
|
||||
updateData({ collateral });
|
||||
};
|
||||
const onCollateralChange = (value: string) =>
|
||||
onStorageRequestChange({ collateral: Number(value) });
|
||||
|
||||
// const pluralizeUnit = () => {
|
||||
// if (data.availability > 1 && !data.availabilityUnit.endsWith("s")) {
|
||||
@ -257,7 +193,7 @@ export function StorageRequestReview({ onChangeNextState }: Props) {
|
||||
// return data.availabilityUnit;
|
||||
// };
|
||||
|
||||
const availability = `${data.availability} ${data.availabilityUnit}`;
|
||||
const availability = `${storageRequest.availability} ${storageRequest.availabilityUnit}`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -265,19 +201,19 @@ export function StorageRequestReview({ onChangeNextState }: Props) {
|
||||
<div className="storageRequestReview-numbers">
|
||||
<CardNumbers
|
||||
title={"Nodes"}
|
||||
data={data.nodes.toString()}
|
||||
data={storageRequest.nodes.toString()}
|
||||
onChange={onNodesChange}
|
||||
onValidation={isInvalidNodes}
|
||||
helper="Number of storage nodes"></CardNumbers>
|
||||
<CardNumbers
|
||||
title={"Tolerance"}
|
||||
data={data.tolerance.toString()}
|
||||
data={storageRequest.tolerance.toString()}
|
||||
onChange={onToleranceChange}
|
||||
onValidation={isInvalidTolerance}
|
||||
helper="Failure node tolerated"></CardNumbers>
|
||||
<CardNumbers
|
||||
title={"Proof probability"}
|
||||
data={data.proofProbability.toString()}
|
||||
data={storageRequest.proofProbability.toString()}
|
||||
onChange={onProofProbabilityChange}
|
||||
helper="Proof request frequency in seconds"></CardNumbers>
|
||||
</div>
|
||||
@ -365,13 +301,13 @@ export function StorageRequestReview({ onChangeNextState }: Props) {
|
||||
helper="Full period of the contract"></CardNumbers>
|
||||
<CardNumbers
|
||||
title={"Collateral"}
|
||||
data={data.collateral.toString()}
|
||||
data={storageRequest.collateral.toString()}
|
||||
onChange={onCollateralChange}
|
||||
onValidation={isInvalidNumber}
|
||||
helper="Reward tokens for hosts"></CardNumbers>
|
||||
<CardNumbers
|
||||
title={"Reward"}
|
||||
data={data.reward.toString()}
|
||||
data={storageRequest.reward.toString()}
|
||||
onChange={onRewardChange}
|
||||
onValidation={isInvalidNumber}
|
||||
helper="Penality tokens"></CardNumbers>
|
||||
@ -393,7 +329,7 @@ export function StorageRequestReview({ onChangeNextState }: Props) {
|
||||
<div className="storageRequestReview-alert">
|
||||
<CardNumbers
|
||||
title={"Expiration"}
|
||||
data={data.expiration.toString()}
|
||||
data={storageRequest.expiration.toString()}
|
||||
onChange={onExpirationChange}
|
||||
className="storageRequestReview-expiration"
|
||||
onValidation={isInvalidNumber}
|
||||
@ -403,8 +339,9 @@ export function StorageRequestReview({ onChangeNextState }: Props) {
|
||||
title="Warning"
|
||||
variant="warning"
|
||||
className="storageRequestReview-alert">
|
||||
If no suitable hosts are found for the CID {cid} matching your storage
|
||||
requirements, you will incur a charge a small amount of tokens.
|
||||
If no suitable hosts are found for the CID {storageRequest.cid}{" "}
|
||||
matching your storage requirements, you will incur a charge a small
|
||||
amount of tokens.
|
||||
</Alert>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,29 +1,3 @@
|
||||
.storageRequest {
|
||||
background-color: var(--codex-background);
|
||||
background-color: var(--codex-background);
|
||||
border-radius: var(--codex-border-radius);
|
||||
transition: transform 0.15s;
|
||||
max-width: 800px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
opacity: 0;
|
||||
z-index: -1;
|
||||
max-height: 100%;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%) scale(0);
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.storageRequest-open {
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
|
||||
.storageRequest-open {
|
||||
opacity: 1;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.storageRequest-title {
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
@media (min-width: 801px) {
|
||||
.storageRequest-stepper {
|
||||
min-width: 700px;
|
||||
}
|
||||
}
|
||||
@ -1,202 +1,153 @@
|
||||
import { StorageRequestFileChooser } from "../../components/StorageRequestSetup/StorageRequestFileChooser";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { WebStorage } from "../../utils/web-storage";
|
||||
import { STEPPER_DURATION } from "../../utils/constants";
|
||||
import { StorageRequestReview } from "./StorageRequestReview";
|
||||
import { CodexCreateStorageRequestInput } from "@codex-storage/sdk-js";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { CodexSdk } from "../../sdk/codex";
|
||||
import { StorageAvailabilityUnit, StorageRequestCriteria } from "./types";
|
||||
import { StorageRequest } from "./types";
|
||||
import {
|
||||
Backdrop,
|
||||
Button,
|
||||
Modal,
|
||||
Stepper,
|
||||
Toast,
|
||||
useStepperReducer,
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import { classnames } from "../../utils/classnames";
|
||||
import { StorageRequestDone } from "./StorageRequestDone";
|
||||
import { PurchaseStorage } from "../../utils/purchases-storage";
|
||||
import { Promises } from "../../utils/promises";
|
||||
import * as Sentry from "@sentry/browser";
|
||||
import { Times } from "../../utils/times";
|
||||
import { useStorageRequestMutation } from "./useStorageRequestMutation";
|
||||
import { ErrorPlaceholder } from "../ErrorPlaceholder/ErrorPlaceholder";
|
||||
import { Plus } from "lucide-react";
|
||||
import "./StorageRequestStepper.css";
|
||||
import { StorageRequestError } from "./StorageRequestError";
|
||||
|
||||
function calculateAvailability(value: number, unit: StorageAvailabilityUnit) {
|
||||
switch (unit) {
|
||||
case "minutes":
|
||||
return 60 * value;
|
||||
case "hours":
|
||||
return 60 * 60 * value;
|
||||
case "days":
|
||||
return 24 * 60 * 60 * value;
|
||||
case "months":
|
||||
return 30 * 24 * 60 * 60 * value;
|
||||
case "years":
|
||||
return 365 * 24 * 60 * 60 * value;
|
||||
}
|
||||
}
|
||||
type Props = {};
|
||||
|
||||
type Props = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
className?: string;
|
||||
const CONFIRM_STATE = 2;
|
||||
const STEPS = 3;
|
||||
|
||||
const defaultStorageRequest: StorageRequest = {
|
||||
cid: "",
|
||||
availabilityUnit: "days",
|
||||
availability: 1,
|
||||
tolerance: 1,
|
||||
proofProbability: 1,
|
||||
nodes: 3,
|
||||
reward: 10,
|
||||
collateral: 10,
|
||||
expiration: 300,
|
||||
};
|
||||
|
||||
const UPLOAD_STEP = 0;
|
||||
const SUCCESS_STEP = 2;
|
||||
|
||||
export function StorageRequestStepper({ className, open, onClose }: Props) {
|
||||
const [progress, setProgress] = useState(true);
|
||||
const [step, setStep] = useState(0);
|
||||
export function StorageRequestStepper({}: Props) {
|
||||
const [storageRequest, setStorageRequest] = useState<StorageRequest>(
|
||||
defaultStorageRequest
|
||||
);
|
||||
const steps = useRef(["File", "Criteria", "Success"]);
|
||||
const [isNextDisable, setIsNextDisable] = useState(true);
|
||||
const queryClient = useQueryClient();
|
||||
const [toast, setToast] = useState({
|
||||
time: 0,
|
||||
message: "",
|
||||
});
|
||||
|
||||
const { mutateAsync } = useMutation({
|
||||
mutationKey: ["debug"],
|
||||
mutationFn: (input: CodexCreateStorageRequestInput) =>
|
||||
CodexSdk.marketplace
|
||||
.createStorageRequest(input)
|
||||
.then((s) => Promises.rejectOnError(s)),
|
||||
onSuccess: async (requestId, { cid }) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["purchases"] });
|
||||
|
||||
if (!requestId.startsWith("0x")) {
|
||||
requestId = "0x" + requestId;
|
||||
}
|
||||
|
||||
PurchaseStorage.set(requestId, cid);
|
||||
WebStorage.set("storage-request-step", SUCCESS_STEP);
|
||||
|
||||
setProgress(false);
|
||||
setStep(SUCCESS_STEP);
|
||||
},
|
||||
onError: (error) => {
|
||||
if (import.meta.env.PROD) {
|
||||
Sentry.captureException(error);
|
||||
}
|
||||
|
||||
setProgress(false);
|
||||
setToast({
|
||||
message: "Error when trying to update: " + error.message,
|
||||
time: Date.now(),
|
||||
});
|
||||
},
|
||||
});
|
||||
const { state, dispatch } = useStepperReducer();
|
||||
const { mutateAsync, error } = useStorageRequestMutation(dispatch, state);
|
||||
|
||||
useEffect(() => {
|
||||
WebStorage.get<number>("storage-request-step").then((value) => {
|
||||
setStep(value || 0);
|
||||
Promise.all([
|
||||
WebStorage.get<number>("storage-request-step"),
|
||||
WebStorage.get<StorageRequest>("storage-request"),
|
||||
]).then(([s, data]) => {
|
||||
if (s) {
|
||||
dispatch({
|
||||
type: "next",
|
||||
step: s,
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
setProgress(false);
|
||||
}, STEPPER_DURATION);
|
||||
if (data) {
|
||||
setStorageRequest(data);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onChangeNextState = useCallback(
|
||||
(s: "enable" | "disable") => setIsNextDisable(s === "disable"),
|
||||
[]
|
||||
);
|
||||
}, [dispatch]);
|
||||
|
||||
const components = [
|
||||
StorageRequestFileChooser,
|
||||
StorageRequestReview,
|
||||
StorageRequestDone,
|
||||
error ? StorageRequestError : StorageRequestDone,
|
||||
];
|
||||
|
||||
const onChangeStep = async (nextStep: number, state: "before" | "end") => {
|
||||
if (nextStep < UPLOAD_STEP) {
|
||||
setStep(0);
|
||||
setIsNextDisable(true);
|
||||
setProgress(false);
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
const onNextStep = async (step: number) => {
|
||||
if (step === components.length) {
|
||||
setStorageRequest(defaultStorageRequest);
|
||||
|
||||
if (state === "before") {
|
||||
setIsNextDisable(true);
|
||||
setProgress(true);
|
||||
setStep(nextStep);
|
||||
return;
|
||||
}
|
||||
|
||||
if (nextStep > SUCCESS_STEP) {
|
||||
WebStorage.delete("storage-request-step");
|
||||
WebStorage.delete("storage-request-criteria");
|
||||
|
||||
setIsNextDisable(true);
|
||||
setProgress(false);
|
||||
setStep(0);
|
||||
|
||||
onClose();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (nextStep == SUCCESS_STEP) {
|
||||
const [cid, criteria] = await Promise.all([
|
||||
WebStorage.get<string>("storage-request-step-1"),
|
||||
WebStorage.get<StorageRequestCriteria>("storage-request-criteria"),
|
||||
]);
|
||||
|
||||
if (!cid || !criteria) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
availabilityUnit = "days",
|
||||
availability,
|
||||
reward,
|
||||
collateral,
|
||||
expiration,
|
||||
nodes,
|
||||
proofProbability,
|
||||
tolerance,
|
||||
} = criteria;
|
||||
|
||||
mutateAsync({
|
||||
cid,
|
||||
collateral,
|
||||
duration: calculateAvailability(availability, availabilityUnit),
|
||||
expiry: expiration * 60,
|
||||
nodes,
|
||||
proofProbability,
|
||||
tolerance,
|
||||
reward,
|
||||
dispatch({
|
||||
step: 0,
|
||||
type: "next",
|
||||
});
|
||||
// TODO When the thread bug will be fixed,
|
||||
// move to the next step without waiting the end of the request
|
||||
// and add a line into the table
|
||||
} else {
|
||||
WebStorage.set("storage-request-step", nextStep);
|
||||
|
||||
setProgress(false);
|
||||
dispatch({
|
||||
type: "close",
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
WebStorage.set("storage-request-step", step);
|
||||
|
||||
if (step == CONFIRM_STATE) {
|
||||
const { availabilityUnit, availability, expiration, ...rest } =
|
||||
storageRequest;
|
||||
mutateAsync({
|
||||
...rest,
|
||||
duration: Times.toSeconds(availability, availabilityUnit),
|
||||
expiry: expiration * 60,
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
step,
|
||||
type: "next",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const Body = progress ? () => <span /> : components[step] || components[0];
|
||||
const onStorageRequestChange = (data: Partial<StorageRequest>) => {
|
||||
const val = { ...storageRequest, ...data };
|
||||
|
||||
WebStorage.set("storage-request", val);
|
||||
|
||||
setStorageRequest(val);
|
||||
};
|
||||
|
||||
const onOpen = () =>
|
||||
dispatch({
|
||||
type: "open",
|
||||
});
|
||||
|
||||
const onClose = () => dispatch({ type: "close" });
|
||||
|
||||
const Body = components[state.step] || (() => <span />);
|
||||
const backLabel = state.step ? "Back" : "Close";
|
||||
const nextLabel = state.step === steps.current.length - 1 ? "Finish" : "Next";
|
||||
|
||||
return (
|
||||
<>
|
||||
<Backdrop open={open} onClose={onClose} removeScroll={true} />
|
||||
<div
|
||||
className={classnames(
|
||||
["storageRequest"],
|
||||
["storageRequest-open", open],
|
||||
[className || ""]
|
||||
)}>
|
||||
<Button
|
||||
label="Storage Request"
|
||||
Icon={Plus}
|
||||
onClick={onOpen}
|
||||
variant="primary"
|
||||
/>
|
||||
|
||||
<Modal open={state.open} onClose={onClose} displayCloseButton={false}>
|
||||
<Stepper
|
||||
titles={steps.current}
|
||||
Body={<Body onChangeNextState={onChangeNextState} />}
|
||||
step={step}
|
||||
onChangeStep={onChangeStep}
|
||||
progress={progress}
|
||||
isNextDisable={progress || isNextDisable}></Stepper>
|
||||
</div>
|
||||
|
||||
<Toast message={toast.message} time={toast.time} variant="error" />
|
||||
state={state}
|
||||
dispatch={dispatch}
|
||||
duration={STEPPER_DURATION}
|
||||
onNextStep={onNextStep}
|
||||
backLabel={backLabel}
|
||||
className="storageRequest-stepper"
|
||||
nextLabel={nextLabel}>
|
||||
<Body
|
||||
dispatch={dispatch}
|
||||
state={state}
|
||||
onStorageRequestChange={onStorageRequestChange}
|
||||
storageRequest={storageRequest}
|
||||
error={error}
|
||||
/>
|
||||
</Stepper>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
import {
|
||||
StepperAction,
|
||||
StepperState,
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import { Dispatch } from "react";
|
||||
|
||||
export type StorageDurabilityStepValue = {
|
||||
tolerance: number;
|
||||
proofProbability: number;
|
||||
@ -29,7 +35,8 @@ export type AvailabilityUnit =
|
||||
| "minutes"
|
||||
| "hours";
|
||||
|
||||
export type StorageRequestCriteria = {
|
||||
export type StorageRequest = {
|
||||
cid: string;
|
||||
availability: number;
|
||||
availabilityUnit: AvailabilityUnit;
|
||||
tolerance: number;
|
||||
@ -39,3 +46,11 @@ export type StorageRequestCriteria = {
|
||||
collateral: number;
|
||||
expiration: number;
|
||||
};
|
||||
|
||||
export type StorageRequestComponentProps = {
|
||||
dispatch: Dispatch<StepperAction>;
|
||||
state: StepperState;
|
||||
onStorageRequestChange: (data: Partial<StorageRequest>) => void;
|
||||
storageRequest: StorageRequest;
|
||||
error: Error | null;
|
||||
};
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
import { CodexCreateStorageRequestInput } from "@codex-storage/sdk-js";
|
||||
import { CodexSdk } from "../../sdk/codex";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { Promises } from "../../utils/promises";
|
||||
import { PurchaseStorage } from "../../utils/purchases-storage";
|
||||
import { WebStorage } from "../../utils/web-storage";
|
||||
import {
|
||||
StepperAction,
|
||||
StepperState,
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import { Dispatch, useState } from "react";
|
||||
import * as Sentry from "@sentry/browser";
|
||||
|
||||
export function useStorageRequestMutation(
|
||||
dispatch: Dispatch<StepperAction>,
|
||||
state: StepperState
|
||||
) {
|
||||
const queryClient = useQueryClient();
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
const { mutateAsync } = useMutation({
|
||||
mutationKey: ["debug"],
|
||||
mutationFn: (input: CodexCreateStorageRequestInput) =>
|
||||
CodexSdk.marketplace
|
||||
.createStorageRequest(input)
|
||||
.then((s) => Promises.rejectOnError(s)),
|
||||
onSuccess: async () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["purchases"] });
|
||||
|
||||
// if (!requestId.startsWith("0x")) {
|
||||
// requestId = "0x" + requestId;
|
||||
// }
|
||||
|
||||
WebStorage.delete("storage-request-step");
|
||||
WebStorage.delete("storage-request");
|
||||
|
||||
// PurchaseStorage.set(requestId, cid);
|
||||
// WebStorage.set("storage-request-step", SUCCESS_STEP);
|
||||
dispatch({
|
||||
type: "next",
|
||||
step: state.step,
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
if (import.meta.env.PROD) {
|
||||
Sentry.captureException(error);
|
||||
}
|
||||
|
||||
setError(error);
|
||||
|
||||
WebStorage.set("storage-request-step", state.step - 1);
|
||||
|
||||
dispatch({
|
||||
type: "next",
|
||||
step: state.step,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return { mutateAsync, error };
|
||||
}
|
||||
@ -1,17 +1,9 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { CodexSdk } from "../../sdk/codex";
|
||||
import { Plus } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Cell,
|
||||
Spinner,
|
||||
Table,
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import { Cell, Spinner, Table } from "@codex-storage/marketplace-ui-components";
|
||||
import { StorageRequestStepper } from "../../components/StorageRequestSetup/StorageRequestStepper";
|
||||
import "./purchases.css";
|
||||
import { classnames } from "../../utils/classnames";
|
||||
import { FileCell } from "../../components/FileCellRender/FileCell";
|
||||
import { CustomStateCellRender } from "../../components/CustomStateCellRender/CustomStateCellRender";
|
||||
import prettyMilliseconds from "pretty-ms";
|
||||
@ -20,7 +12,6 @@ import { Promises } from "../../utils/promises";
|
||||
import { TruncateCell } from "../../components/TruncateCell/TruncateCell";
|
||||
|
||||
const Purchases = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const { data, isPending } = useQuery({
|
||||
queryFn: () =>
|
||||
CodexSdk.marketplace.purchases().then((s) => Promises.rejectOnError(s)),
|
||||
@ -67,38 +58,21 @@ const Purchases = () => {
|
||||
];
|
||||
}) || [];
|
||||
|
||||
// TODO make name uniforms
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="purchases-actions">
|
||||
<Button
|
||||
label="Storage Request"
|
||||
Icon={Plus}
|
||||
onClick={() => setOpen(true)}
|
||||
variant="primary"
|
||||
/>
|
||||
<StorageRequestStepper />
|
||||
</div>
|
||||
|
||||
<StorageRequestStepper
|
||||
className={classnames(
|
||||
["purchases-modal"],
|
||||
["purchases-modal-open", open]
|
||||
)}
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
/>
|
||||
|
||||
<Table headers={headers} cells={cells} />
|
||||
|
||||
{/* {!cells.length && (
|
||||
<EmptyPlaceholder
|
||||
title="Nothing to show"
|
||||
message="No data here yet. Start to upload files to see data here."
|
||||
/>
|
||||
)} */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// TODO make uniforms for availabilities
|
||||
|
||||
export const Route = createFileRoute("/dashboard/purchases")({
|
||||
component: () => (
|
||||
<ErrorBoundary card={true}>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user