Refactor storage request

This commit is contained in:
Arnaud 2024-09-24 18:57:52 +02:00
parent 655cbbbaa0
commit 932ca9d4e3
No known key found for this signature in database
GPG Key ID: 69D6CE281FCAE663
12 changed files with 344 additions and 399 deletions

View File

@ -67,6 +67,11 @@ export function AvailabilityCreate({ space }: Props) {
if (step === components.length) {
setAvailability(defaultAvailabilityData);
dispatch({
step: 0,
type: "next",
});
dispatch({
type: "close",
});

View File

@ -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

View 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}
/>
);
}

View File

@ -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]
)}
/>

View File

@ -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;

View File

@ -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>

View File

@ -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;

View File

@ -0,0 +1,5 @@
@media (min-width: 801px) {
.storageRequest-stepper {
min-width: 700px;
}
}

View File

@ -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>
</>
);
}

View File

@ -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;
};

View File

@ -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 };
}

View File

@ -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}>