mirror of
https://github.com/logos-storage/logos-storage-marketplace-ui.git
synced 2026-05-21 00:39:26 +00:00
Create availability
This commit is contained in:
parent
4c60f36568
commit
508977120d
28
package-lock.json
generated
28
package-lock.json
generated
@ -9,8 +9,8 @@
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codex-storage/marketplace-ui-components": "0.0.4",
|
||||
"@codex-storage/sdk-js": "0.0.3",
|
||||
"@codex-storage/marketplace-ui-components": "0.0.5",
|
||||
"@codex-storage/sdk-js": "0.0.4",
|
||||
"@sentry/react": "^8.27.0",
|
||||
"@tanstack/react-query": "^5.51.15",
|
||||
"@tanstack/react-router": "^1.45.7",
|
||||
@ -33,7 +33,7 @@
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-react-refresh": "^0.4.7",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "^5.2.2",
|
||||
"typescript": "^5.5.2",
|
||||
"vite": "^5.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@ -343,31 +343,34 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codex-storage/marketplace-ui-components": {
|
||||
"version": "0.0.4",
|
||||
"license": "MIT",
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@codex-storage/marketplace-ui-components/-/marketplace-ui-components-0.0.5.tgz",
|
||||
"integrity": "sha512-YrT8glXA6Dt/gsgyALzhHYBVdCFlVFRaahkBAwvVV7rpnvh7eeIdObSq6+m4kyEGALLIh/wVZ4OIfD1bG0dXOQ==",
|
||||
"dependencies": {
|
||||
"lucide-react": "^0.428.0"
|
||||
"lucide-react": "^0.441.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@codex-storage/sdk-js": "0.0.3",
|
||||
"@codex-storage/sdk-js": "0.0.4",
|
||||
"@tanstack/react-query": "^5.51.24",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@codex-storage/marketplace-ui-components/node_modules/lucide-react": {
|
||||
"version": "0.428.0",
|
||||
"license": "ISC",
|
||||
"version": "0.441.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.441.0.tgz",
|
||||
"integrity": "sha512-0vfExYtvSDhkC2lqg0zYVW1Uu9GsI4knuV9GP9by5z0Xhc4Zi5RejTxfz9LsjRmCyWVzHCJvxGKZWcRyvQCWVg==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@codex-storage/sdk-js": {
|
||||
"version": "0.0.3",
|
||||
"license": "MIT",
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@codex-storage/sdk-js/-/sdk-js-0.0.4.tgz",
|
||||
"integrity": "sha512-RKhghVmVDSbDKrof5D89KOkif/mhNy87oYPrguC04W0OChBxFoDTFeuWmEbL3e9mVhOObGoLRB6dMkFBEup2vw==",
|
||||
"dependencies": {
|
||||
"valibot": "^0.36.0"
|
||||
},
|
||||
@ -3530,7 +3533,8 @@
|
||||
},
|
||||
"node_modules/valibot": {
|
||||
"version": "0.36.0",
|
||||
"license": "MIT"
|
||||
"resolved": "https://registry.npmjs.org/valibot/-/valibot-0.36.0.tgz",
|
||||
"integrity": "sha512-CjF1XN4sUce8sBK9TixrDqFM7RwNkuXdJu174/AwmQUB62QbCQADg5lLe8ldBalFgtj1uKj+pKwDJiNo4Mn+eQ=="
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.4.5",
|
||||
|
||||
@ -23,8 +23,8 @@
|
||||
"React"
|
||||
],
|
||||
"dependencies": {
|
||||
"@codex-storage/marketplace-ui-components": "0.0.4",
|
||||
"@codex-storage/sdk-js": "0.0.3",
|
||||
"@codex-storage/marketplace-ui-components": "0.0.5",
|
||||
"@codex-storage/sdk-js": "0.0.4",
|
||||
"@sentry/react": "^8.27.0",
|
||||
"@tanstack/react-query": "^5.51.15",
|
||||
"@tanstack/react-router": "^1.45.7",
|
||||
@ -47,7 +47,7 @@
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-react-refresh": "^0.4.7",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "^5.2.2",
|
||||
"typescript": "^5.5.2",
|
||||
"vite": "^5.3.4"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
5
src/components/Availability/AvailabilityConfirm.css
Normal file
5
src/components/Availability/AvailabilityConfirm.css
Normal file
@ -0,0 +1,5 @@
|
||||
.availabilitConfirm-title {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
60
src/components/Availability/AvailabilityConfirmation.tsx
Normal file
60
src/components/Availability/AvailabilityConfirmation.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import {
|
||||
SpaceAllocation,
|
||||
StepperAction,
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import { Dispatch, useEffect } from "react";
|
||||
import "./AvailabilityForm.css";
|
||||
import { CodexNodeSpace } from "@codex-storage/sdk-js";
|
||||
import { UIAvailability } from "./types";
|
||||
import { GB, TB } from "../../utils/constants";
|
||||
import "./AvailabilityConfirm.css";
|
||||
|
||||
type Props = {
|
||||
dispatch: Dispatch<StepperAction>;
|
||||
space: CodexNodeSpace;
|
||||
availability: UIAvailability;
|
||||
enableNext?: boolean;
|
||||
};
|
||||
|
||||
export function AvailabilityConfirm({
|
||||
availability,
|
||||
dispatch,
|
||||
space,
|
||||
enableNext = true,
|
||||
}: Props) {
|
||||
useEffect(() => {
|
||||
if (enableNext) {
|
||||
dispatch({ type: "toggle-next", isNextEnable: true });
|
||||
}
|
||||
}, [dispatch, enableNext]);
|
||||
|
||||
const unit = availability.totalSizeUnit === "gb" ? GB : TB;
|
||||
const { quotaMaxBytes, quotaReservedBytes } = space;
|
||||
const size = availability.totalSize * unit;
|
||||
const isUpdating = !!availability.id;
|
||||
const allocated = isUpdating ? quotaReservedBytes - size : quotaReservedBytes;
|
||||
const remaining = quotaMaxBytes - allocated - size;
|
||||
|
||||
const spaceData = [
|
||||
{
|
||||
title: "Space allocated",
|
||||
size: allocated,
|
||||
},
|
||||
{
|
||||
title: "New space allocation",
|
||||
size: size,
|
||||
},
|
||||
{
|
||||
title: "Remaining space",
|
||||
size: remaining < 0 ? 0 : remaining,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<b className="availabilitConfirm-title">Node space allocation</b>
|
||||
|
||||
<SpaceAllocation data={spaceData} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
132
src/components/Availability/AvailabilityCreate.tsx
Normal file
132
src/components/Availability/AvailabilityCreate.tsx
Normal file
@ -0,0 +1,132 @@
|
||||
import {
|
||||
Stepper,
|
||||
Toast,
|
||||
useStepperReducer,
|
||||
Button,
|
||||
Modal,
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { AvailabilityForm } from "./AvailabilityForm";
|
||||
import { Plus } from "lucide-react";
|
||||
import { CodexNodeSpace } from "@codex-storage/sdk-js";
|
||||
import { AvailabilityConfirm } from "./AvailabilityConfirmation";
|
||||
import { WebStorage } from "../../utils/web-storage";
|
||||
import { UIAvailability } from "./types";
|
||||
import { STEPPER_DURATION } from "../../utils/constants";
|
||||
import { useAvailabilityMutation } from "./useAvailabilityMutation";
|
||||
import { AvailabilityDone } from "./AvailabilityDone";
|
||||
|
||||
type Props = {
|
||||
space: CodexNodeSpace;
|
||||
};
|
||||
|
||||
const CONFIRM_STATE = 2;
|
||||
|
||||
export function AvailabilityCreate({ space }: Props) {
|
||||
const components = [AvailabilityForm, AvailabilityConfirm, AvailabilityDone];
|
||||
const steps = useRef(["Availability", "Confirmation", "Success"]);
|
||||
const { state, dispatch } = useStepperReducer(components.length);
|
||||
const [availability, setAvailability] = useState<UIAvailability>({
|
||||
totalSize: 1,
|
||||
duration: 1,
|
||||
minPrice: 0,
|
||||
maxCollateral: 0,
|
||||
totalSizeUnit: "gb",
|
||||
durationUnit: "days",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
WebStorage.get<number>("availability-step"),
|
||||
WebStorage.get<UIAvailability>("availability"),
|
||||
]).then(([s, a]) => {
|
||||
if (s) {
|
||||
dispatch({
|
||||
type: "next",
|
||||
step: s,
|
||||
});
|
||||
}
|
||||
|
||||
if (a) {
|
||||
setAvailability(a);
|
||||
}
|
||||
|
||||
// TODO validationb
|
||||
dispatch({
|
||||
type: "toggle-next",
|
||||
isNextEnable: true,
|
||||
});
|
||||
});
|
||||
}, [dispatch]);
|
||||
|
||||
const { mutateAsync, toast } = useAvailabilityMutation(dispatch, state);
|
||||
|
||||
const onNextStep = async (step: number) => {
|
||||
WebStorage.set("availability-step", step);
|
||||
|
||||
if (step == CONFIRM_STATE) {
|
||||
mutateAsync(availability);
|
||||
} else {
|
||||
dispatch({
|
||||
step,
|
||||
type: "next",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onAvailabilityChange = (
|
||||
data: Partial<UIAvailability>,
|
||||
valid: boolean
|
||||
) => {
|
||||
const val = { ...availability, ...data };
|
||||
|
||||
if (valid) {
|
||||
WebStorage.set("availability", val);
|
||||
}
|
||||
|
||||
setAvailability(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 (
|
||||
<>
|
||||
<Button
|
||||
label="Availability"
|
||||
Icon={Plus}
|
||||
onClick={onOpen}
|
||||
variant="primary"
|
||||
/>
|
||||
|
||||
<Modal open={state.open} onClose={onClose} displayCloseButton={false}>
|
||||
<Stepper
|
||||
titles={steps.current}
|
||||
state={state}
|
||||
dispatch={dispatch}
|
||||
duration={STEPPER_DURATION}
|
||||
onNextStep={onNextStep}
|
||||
backLabel={backLabel}
|
||||
nextLabel={nextLabel}>
|
||||
<Body
|
||||
dispatch={dispatch}
|
||||
state={state}
|
||||
onAvailabilityChange={onAvailabilityChange}
|
||||
availability={availability}
|
||||
space={space}
|
||||
/>
|
||||
</Stepper>
|
||||
</Modal>
|
||||
|
||||
<Toast message={toast.message} time={toast.time} variant="error" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
7
src/components/Availability/AvailabilityDone.css
Normal file
7
src/components/Availability/AvailabilityDone.css
Normal file
@ -0,0 +1,7 @@
|
||||
.availabilityDone {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.availabilityDone-icon {
|
||||
color: var(--codex-color-primary);
|
||||
}
|
||||
28
src/components/Availability/AvailabilityDone.tsx
Normal file
28
src/components/Availability/AvailabilityDone.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import {
|
||||
Placeholder,
|
||||
StepperAction,
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import { CircleCheck } from "lucide-react";
|
||||
import { Dispatch, useEffect } from "react";
|
||||
import "./AvailabilityDone.css";
|
||||
|
||||
type Props = {
|
||||
dispatch: Dispatch<StepperAction>;
|
||||
};
|
||||
|
||||
export function AvailabilityDone({ dispatch }: Props) {
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
isNextEnable: true,
|
||||
type: "toggle-next",
|
||||
});
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<Placeholder
|
||||
Icon={<CircleCheck size="4rem" className="availabilityDone-icon" />}
|
||||
className="availabilityDone"
|
||||
title="Your availability is created."
|
||||
message="The new availability will appear in your availability list. You can safely close this dialog."></Placeholder>
|
||||
);
|
||||
}
|
||||
19
src/components/Availability/AvailabilityForm.css
Normal file
19
src/components/Availability/AvailabilityForm.css
Normal file
@ -0,0 +1,19 @@
|
||||
.availabilityForm-itemInput {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.availabilityForm-item {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.availabilityForm-item--error .input,
|
||||
.availabilityForm-item--error .inputGroup-helper,
|
||||
.availabilityForm-item--error .inputGroup-select {
|
||||
color: rgb(var(--codex-color-error));
|
||||
border-color: rgb(var(--codex-color-error));
|
||||
}
|
||||
|
||||
.availabilityForm-row {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
197
src/components/Availability/AvailabilityForm.tsx
Normal file
197
src/components/Availability/AvailabilityForm.tsx
Normal file
@ -0,0 +1,197 @@
|
||||
import {
|
||||
Input,
|
||||
InputGroup,
|
||||
StepperAction,
|
||||
StepperState,
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import { ChangeEvent, Dispatch, useState } from "react";
|
||||
import "./AvailabilityForm.css";
|
||||
import { CodexNodeSpace } from "@codex-storage/sdk-js";
|
||||
import { UIAvailability } from "./types";
|
||||
import { GB, TB } from "../../utils/constants";
|
||||
import { classnames } from "../../utils/classnames";
|
||||
import { AvailabilityConfirm } from "./AvailabilityConfirmation";
|
||||
|
||||
type Props = {
|
||||
dispatch: Dispatch<StepperAction>;
|
||||
state: StepperState;
|
||||
space: CodexNodeSpace;
|
||||
onAvailabilityChange: (data: Partial<UIAvailability>, valid: boolean) => void;
|
||||
availability: UIAvailability;
|
||||
};
|
||||
|
||||
export function AvailabilityForm({
|
||||
dispatch,
|
||||
onAvailabilityChange,
|
||||
availability,
|
||||
space,
|
||||
}: Props) {
|
||||
const [totalSizeError, setTotalSizeError] = useState("");
|
||||
|
||||
const onTotalSizeUnitChange = async (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
const element = e.currentTarget;
|
||||
const valid = element.value === "tb" || element.value === "gb";
|
||||
|
||||
dispatch({
|
||||
type: "toggle-next",
|
||||
isNextEnable: false,
|
||||
});
|
||||
|
||||
onAvailabilityChange(
|
||||
{
|
||||
totalSize: 0,
|
||||
totalSizeUnit: element.value as "tb" | "gb",
|
||||
},
|
||||
valid
|
||||
);
|
||||
};
|
||||
|
||||
const onDurationUnitChange = async (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
const element = e.currentTarget;
|
||||
const valid =
|
||||
element.value === "hours" ||
|
||||
element.value === "days" ||
|
||||
element.value === "months";
|
||||
|
||||
onAvailabilityChange(
|
||||
{
|
||||
duration: 1,
|
||||
durationUnit: element.value as "hours" | "days" | "months",
|
||||
},
|
||||
valid
|
||||
);
|
||||
};
|
||||
|
||||
const onAvailablityChange = async (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const element = e.currentTarget;
|
||||
const valid = element.checkValidity();
|
||||
const val = parseFloat(element.value);
|
||||
const unit = availability.totalSizeUnit === "gb" ? GB : TB;
|
||||
|
||||
if (val * unit > space.quotaMaxBytes - space.quotaReservedBytes) {
|
||||
setTotalSizeError(
|
||||
"You cannot allocate more space than the remaining space."
|
||||
);
|
||||
dispatch({
|
||||
type: "toggle-next",
|
||||
isNextEnable: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
onAvailabilityChange(
|
||||
{
|
||||
[element.name]: parseFloat(element.value),
|
||||
},
|
||||
valid
|
||||
);
|
||||
setTotalSizeError(valid ? "" : element.validationMessage);
|
||||
dispatch({
|
||||
type: "toggle-next",
|
||||
isNextEnable: valid && parseFloat(e.target.value) > 0,
|
||||
});
|
||||
};
|
||||
|
||||
const onInputChange = async (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const element = e.currentTarget;
|
||||
const valid = element.checkValidity();
|
||||
|
||||
onAvailabilityChange(
|
||||
{
|
||||
[element.name]: parseFloat(element.value),
|
||||
},
|
||||
valid
|
||||
);
|
||||
};
|
||||
|
||||
const unit = availability.totalSizeUnit === "gb" ? GB : TB;
|
||||
const max = space.quotaMaxBytes / unit - space.quotaReservedBytes / unit;
|
||||
|
||||
return (
|
||||
<>
|
||||
<AvailabilityConfirm
|
||||
availability={availability}
|
||||
dispatch={dispatch}
|
||||
space={space}
|
||||
enableNext={false}
|
||||
/>
|
||||
|
||||
<InputGroup
|
||||
id="totalSize"
|
||||
name="totalSize"
|
||||
type="number"
|
||||
label="Total size"
|
||||
helper={
|
||||
totalSizeError || "Total size of availability's storage in bytes"
|
||||
}
|
||||
className={classnames(
|
||||
["availabilityForm-item"],
|
||||
["availabilityForm-item--error", !!totalSizeError]
|
||||
)}
|
||||
inputClassName="availabilityForm-itemInput"
|
||||
min={0.01}
|
||||
max={max}
|
||||
onChange={onAvailablityChange}
|
||||
onGroupChange={onTotalSizeUnitChange}
|
||||
value={availability.totalSize.toString()}
|
||||
step={"0.01"}
|
||||
group={[
|
||||
["gb", "GB"],
|
||||
["tb", "TB"],
|
||||
]}
|
||||
groupValue={availability.totalSizeUnit}
|
||||
/>
|
||||
|
||||
<div className="availabilityForm-item">
|
||||
<InputGroup
|
||||
id="duration"
|
||||
name="duration"
|
||||
type="number"
|
||||
label="Duration"
|
||||
helper="The duration of the request in seconds"
|
||||
inputClassName="availabilityForm-itemInput"
|
||||
min={1}
|
||||
onChange={onInputChange}
|
||||
onGroupChange={onDurationUnitChange}
|
||||
group={[
|
||||
["hours", "Hours"],
|
||||
["days", "Days"],
|
||||
["months", "Months"],
|
||||
]}
|
||||
value={availability.duration.toString()}
|
||||
groupValue={availability.durationUnit}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="availabilityForm-row">
|
||||
<div className="availabilityForm-item">
|
||||
<Input
|
||||
id="minPrice"
|
||||
name="minPrice"
|
||||
type="number"
|
||||
label="Min price"
|
||||
min={0}
|
||||
helper="Minimum price to be paid (in amount of tokens)"
|
||||
inputClassName="availabilityForm-itemInput"
|
||||
onChange={onInputChange}
|
||||
value={availability.minPrice.toString()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="availabilityForm-item">
|
||||
<Input
|
||||
id="maxCollateral"
|
||||
name="maxCollateral"
|
||||
type="number"
|
||||
label="Max collateral"
|
||||
min={0}
|
||||
helper="Maximum collateral user is willing to pay per filled Slot (in amount of tokens)"
|
||||
inputClassName="availabilityForm-itemInput"
|
||||
onChange={onInputChange}
|
||||
value={availability.maxCollateral.toString()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
9
src/components/Availability/types.tsx
Normal file
9
src/components/Availability/types.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
export type UIAvailability = {
|
||||
id?: string;
|
||||
totalSize: number;
|
||||
duration: number;
|
||||
durationUnit: "hours" | "days" | "months";
|
||||
minPrice: number;
|
||||
maxCollateral: number;
|
||||
totalSizeUnit: "gb" | "tb";
|
||||
};
|
||||
76
src/components/Availability/useAvailabilityMutation.ts
Normal file
76
src/components/Availability/useAvailabilityMutation.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { CodexSdk } from "../../proxy";
|
||||
import { GB, TB } from "../../utils/constants";
|
||||
import { Promises } from "../../utils/promises";
|
||||
import { WebStorage } from "../../utils/web-storage";
|
||||
import { UIAvailability } from "./types";
|
||||
import { Dispatch, useState } from "react";
|
||||
import {
|
||||
StepperAction,
|
||||
StepperState,
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import * as Sentry from "@sentry/browser";
|
||||
import { SafeValue } from "@codex-storage/sdk-js/async";
|
||||
import { Times } from "../../utils/times";
|
||||
|
||||
export function useAvailabilityMutation(
|
||||
dispatch: Dispatch<StepperAction>,
|
||||
state: StepperState
|
||||
) {
|
||||
const queryClient = useQueryClient();
|
||||
const [toast, setToast] = useState({
|
||||
time: 0,
|
||||
message: "",
|
||||
});
|
||||
|
||||
const { mutateAsync } = useMutation({
|
||||
mutationKey: ["debug"],
|
||||
mutationFn: ({
|
||||
totalSize,
|
||||
totalSizeUnit,
|
||||
duration,
|
||||
durationUnit = "days",
|
||||
...input
|
||||
}: UIAvailability) => {
|
||||
const unit = totalSizeUnit === "gb" ? GB : TB;
|
||||
const marketplace = CodexSdk.marketplace;
|
||||
const time = Times.toMs(duration, durationUnit);
|
||||
|
||||
const fn: (
|
||||
input: Omit<UIAvailability, "totalSizeUnit" | "durationUnit">
|
||||
) => Promise<SafeValue<unknown>> = input.id
|
||||
? (input) =>
|
||||
marketplace.updateAvailability({ ...input, id: input.id || "" })
|
||||
: (input) => marketplace.createAvailability(input);
|
||||
|
||||
return fn({
|
||||
...input,
|
||||
duration: time,
|
||||
totalSize: Math.trunc(totalSize * unit),
|
||||
}).then((s) => Promises.rejectOnError(s));
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["availabilities"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["space"] });
|
||||
|
||||
WebStorage.delete("availability");
|
||||
|
||||
dispatch({
|
||||
type: "next",
|
||||
step: state.step,
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
if (import.meta.env.PROD) {
|
||||
Sentry.captureException(error);
|
||||
}
|
||||
|
||||
setToast({
|
||||
message: "Error when trying to update: " + error.message,
|
||||
time: Date.now(),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return { mutateAsync, toast };
|
||||
}
|
||||
@ -29,17 +29,14 @@ export function NodeSpaceAllocation() {
|
||||
data={[
|
||||
{
|
||||
title: "Maximum storage space used by the node",
|
||||
percent: 60,
|
||||
size: quotaMaxBytes,
|
||||
},
|
||||
{
|
||||
title: "Amount of storage space currently in use",
|
||||
percent: (quotaUsedBytes / quotaMaxBytes) * 100,
|
||||
size: quotaUsedBytes,
|
||||
},
|
||||
{
|
||||
title: "Amount of storage space reserved",
|
||||
percent: (quotaReservedBytes / quotaMaxBytes) * 100,
|
||||
size: quotaReservedBytes,
|
||||
},
|
||||
]}></SpaceAllocation>
|
||||
|
||||
@ -1,15 +1,43 @@
|
||||
import { EmptyPlaceholder } from "@codex-storage/marketplace-ui-components";
|
||||
import { Placeholder } from "@codex-storage/marketplace-ui-components";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import "./availabilities.css";
|
||||
import { CircleX } from "lucide-react";
|
||||
import { AvailabilityCreate } from "../../components/Availability/AvailabilityCreate";
|
||||
import { Promises } from "../../utils/promises";
|
||||
import { CodexSdk } from "../../sdk/codex";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
export const Route = createFileRoute("/dashboard/availabilities")({
|
||||
component: () => {
|
||||
const {
|
||||
data = {
|
||||
quotaMaxBytes: 0,
|
||||
quotaReservedBytes: 0,
|
||||
quotaUsedBytes: 0,
|
||||
totalBlocks: 0,
|
||||
},
|
||||
isError,
|
||||
error,
|
||||
} = useQuery({
|
||||
queryFn: () =>
|
||||
CodexSdk.data.space().then((s) => Promises.rejectOnError(s)),
|
||||
queryKey: ["space"],
|
||||
});
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<Placeholder
|
||||
Icon={<CircleX size={"4em"}></CircleX>}
|
||||
title="Something went wrong"
|
||||
message={error.message}></Placeholder>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="availabilities">
|
||||
<EmptyPlaceholder
|
||||
title="Nothing to show"
|
||||
message="This page is in progress."
|
||||
/>
|
||||
<div className="container">
|
||||
<div className="availabilities-actions">
|
||||
<AvailabilityCreate space={data} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { Codex } from "@codex-storage/sdk-js";
|
||||
import { WebStorage } from "../utils/web-storage";
|
||||
|
||||
let client: Codex;
|
||||
let url: string;
|
||||
let client: Codex = new Codex(import.meta.env.VITE_CODEX_API_URL);
|
||||
let url: string = import.meta.env.VITE_CODEX_API_URL;
|
||||
|
||||
export const CodexSdk = {
|
||||
url() {
|
||||
@ -10,19 +10,20 @@ export const CodexSdk = {
|
||||
},
|
||||
|
||||
load() {
|
||||
return WebStorage.get<string>("codex-node-url").then((url) => {
|
||||
url = url || import.meta.env.VITE_CODEX_API_URL;
|
||||
return WebStorage.get<string>("codex-node-url").then((u) => {
|
||||
url = u || import.meta.env.VITE_CODEX_API_URL;
|
||||
client = new Codex(url);
|
||||
});
|
||||
},
|
||||
|
||||
updateURL(url: string) {
|
||||
url = url;
|
||||
updateURL(u: string) {
|
||||
url = u;
|
||||
client = new Codex(url);
|
||||
|
||||
return WebStorage.set("codex-node-url", url);
|
||||
},
|
||||
|
||||
// TODO Change this
|
||||
get debug() {
|
||||
return client.debug;
|
||||
},
|
||||
|
||||
@ -9,3 +9,7 @@ export const ICON_SIZE = "1.25rem";
|
||||
export const STEPPER_DURATION = 500;
|
||||
|
||||
export const EXPLORER_URL = "https://explorer.testnet.codex.storage/tx";
|
||||
|
||||
export const GB = 1_073_741_824;
|
||||
|
||||
export const TB = 1_099_511_627_776;
|
||||
|
||||
18
src/utils/times.ts
Normal file
18
src/utils/times.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export type TimeUnit = "days" | "months" | "years" | "minutes" | "hours";
|
||||
|
||||
export const Times = {
|
||||
toMs(value: number, unit: TimeUnit) {
|
||||
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;
|
||||
}
|
||||
},
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user