mirror of
https://github.com/logos-storage/logos-storage-marketplace-ui.git
synced 2026-01-07 07:53:10 +00:00
Fix stepper steps
This commit is contained in:
parent
298a799e03
commit
655cbbbaa0
@ -1,30 +1,26 @@
|
||||
import { 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 { AvailabilityComponentProps } from "./types";
|
||||
import "./AvailabilityConfirm.css";
|
||||
import { Info } from "lucide-react";
|
||||
import { AvailabilitySpaceAllocation } from "./AvailabilitySpaceAllocation";
|
||||
|
||||
type Props = {
|
||||
dispatch: Dispatch<StepperAction>;
|
||||
space: CodexNodeSpace;
|
||||
availability: UIAvailability;
|
||||
enableNext?: boolean;
|
||||
};
|
||||
import { useEffect } from "react";
|
||||
|
||||
export function AvailabilityConfirm({
|
||||
availability,
|
||||
dispatch,
|
||||
availability,
|
||||
space,
|
||||
enableNext = true,
|
||||
}: Props) {
|
||||
}: AvailabilityComponentProps) {
|
||||
useEffect(() => {
|
||||
if (enableNext) {
|
||||
dispatch({ type: "toggle-next", isNextEnable: true });
|
||||
}
|
||||
}, [dispatch, enableNext]);
|
||||
dispatch({
|
||||
type: "toggle-next",
|
||||
isNextEnable: true,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: "toggle-back",
|
||||
isBackEnable: true,
|
||||
});
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -10,48 +10,39 @@ 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 { AvailabilityState } from "./types";
|
||||
import { STEPPER_DURATION } from "../../utils/constants";
|
||||
import { useAvailabilityMutation } from "./useAvailabilityMutation";
|
||||
import { AvailabilitySuccess } from "./AvailabilitySuccess";
|
||||
import { ErrorPlaceholder } from "../ErrorPlaceholder/ErrorPlaceholder";
|
||||
import { AvailabilityError } from "./AvailabilityError";
|
||||
|
||||
type Props = {
|
||||
space: CodexNodeSpace;
|
||||
};
|
||||
|
||||
const CONFIRM_STATE = 2;
|
||||
const STEPS = 3;
|
||||
|
||||
const defaultAvailabilityData: AvailabilityState = {
|
||||
totalSize: 1,
|
||||
duration: 1,
|
||||
minPrice: 0,
|
||||
maxCollateral: 0,
|
||||
totalSizeUnit: "gb",
|
||||
durationUnit: "days",
|
||||
};
|
||||
|
||||
export function AvailabilityCreate({ space }: Props) {
|
||||
const steps = useRef(["Availability", "Confirmation", "Success"]);
|
||||
const [availability, setAvailability] = useState<UIAvailability>({
|
||||
totalSize: 1,
|
||||
duration: 1,
|
||||
minPrice: 0,
|
||||
maxCollateral: 0,
|
||||
totalSizeUnit: "gb",
|
||||
durationUnit: "days",
|
||||
});
|
||||
const { state, dispatch } = useStepperReducer(STEPS);
|
||||
const [availability, setAvailability] = useState<AvailabilityState>(
|
||||
defaultAvailabilityData
|
||||
);
|
||||
const { state, dispatch } = useStepperReducer();
|
||||
const { mutateAsync, error } = useAvailabilityMutation(dispatch, state);
|
||||
const components = [
|
||||
AvailabilityForm,
|
||||
AvailabilityConfirm,
|
||||
error
|
||||
? () => (
|
||||
<ErrorPlaceholder
|
||||
subtitle="Error when trying to create availability."
|
||||
error={error}
|
||||
/>
|
||||
)
|
||||
: AvailabilitySuccess,
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
WebStorage.get<number>("availability-step"),
|
||||
WebStorage.get<UIAvailability>("availability"),
|
||||
WebStorage.get<AvailabilityState>("availability"),
|
||||
]).then(([s, a]) => {
|
||||
if (s) {
|
||||
dispatch({
|
||||
@ -63,15 +54,26 @@ export function AvailabilityCreate({ space }: Props) {
|
||||
if (a) {
|
||||
setAvailability(a);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: "toggle-next",
|
||||
isNextEnable: true,
|
||||
});
|
||||
});
|
||||
}, [dispatch]);
|
||||
|
||||
const components = [
|
||||
AvailabilityForm,
|
||||
AvailabilityConfirm,
|
||||
error ? AvailabilityError : AvailabilitySuccess,
|
||||
];
|
||||
|
||||
const onNextStep = async (step: number) => {
|
||||
if (step === components.length) {
|
||||
setAvailability(defaultAvailabilityData);
|
||||
|
||||
dispatch({
|
||||
type: "close",
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
WebStorage.set("availability-step", step);
|
||||
|
||||
if (step == CONFIRM_STATE) {
|
||||
@ -81,21 +83,10 @@ export function AvailabilityCreate({ space }: Props) {
|
||||
step,
|
||||
type: "next",
|
||||
});
|
||||
|
||||
if (step === components.length) {
|
||||
setAvailability({
|
||||
totalSize: 1,
|
||||
duration: 1,
|
||||
minPrice: 0,
|
||||
maxCollateral: 0,
|
||||
totalSizeUnit: "gb",
|
||||
durationUnit: "days",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onAvailabilityChange = (data: Partial<UIAvailability>) => {
|
||||
const onAvailabilityChange = (data: Partial<AvailabilityState>) => {
|
||||
const val = { ...availability, ...data };
|
||||
|
||||
WebStorage.set("availability", val);
|
||||
@ -138,6 +129,7 @@ export function AvailabilityCreate({ space }: Props) {
|
||||
onAvailabilityChange={onAvailabilityChange}
|
||||
availability={availability}
|
||||
space={space}
|
||||
error={error}
|
||||
/>
|
||||
</Stepper>
|
||||
</Modal>
|
||||
|
||||
27
src/components/Availability/AvailabilityError.tsx
Normal file
27
src/components/Availability/AvailabilityError.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { AvailabilityComponentProps } from "./types";
|
||||
import { useEffect } from "react";
|
||||
import { ErrorPlaceholder } from "../ErrorPlaceholder/ErrorPlaceholder";
|
||||
|
||||
export function AvailabilityError({
|
||||
dispatch,
|
||||
error,
|
||||
}: AvailabilityComponentProps) {
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: "toggle-next",
|
||||
isNextEnable: false,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: "toggle-back",
|
||||
isBackEnable: true,
|
||||
});
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<ErrorPlaceholder
|
||||
subtitle="Error when trying to create availability."
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -1,59 +1,35 @@
|
||||
import {
|
||||
Input,
|
||||
InputGroup,
|
||||
StepperAction,
|
||||
StepperState,
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import { ChangeEvent, Dispatch, useCallback, useEffect, useState } from "react";
|
||||
import { Input, InputGroup } from "@codex-storage/marketplace-ui-components";
|
||||
import { ChangeEvent, useEffect } from "react";
|
||||
import "./AvailabilityForm.css";
|
||||
import { CodexNodeSpace } from "@codex-storage/sdk-js";
|
||||
import { UIAvailability } from "./types";
|
||||
import { GB, TB } from "../../utils/constants";
|
||||
import { AvailabilityComponentProps } from "./types";
|
||||
import { classnames } from "../../utils/classnames";
|
||||
import { AvailabilitySpaceAllocation } from "./AvailabilitySpaceAllocation";
|
||||
|
||||
type Props = {
|
||||
dispatch: Dispatch<StepperAction>;
|
||||
state: StepperState;
|
||||
space: CodexNodeSpace;
|
||||
onAvailabilityChange: (data: Partial<UIAvailability>) => void;
|
||||
availability: UIAvailability;
|
||||
};
|
||||
import { availabilityMax, isAvailabilityValid } from "./availability.domain";
|
||||
|
||||
export function AvailabilityForm({
|
||||
dispatch,
|
||||
onAvailabilityChange,
|
||||
availability,
|
||||
space,
|
||||
}: Props) {
|
||||
const [totalSizeError, setTotalSizeError] = useState("");
|
||||
|
||||
const isAvailabilityInvalid = useCallback(
|
||||
(value: number) => {
|
||||
const unit = availability.totalSizeUnit === "gb" ? GB : TB;
|
||||
return value * unit > space.quotaMaxBytes - space.quotaReservedBytes;
|
||||
},
|
||||
[space, availability]
|
||||
);
|
||||
|
||||
}: AvailabilityComponentProps) {
|
||||
useEffect(() => {
|
||||
if (isAvailabilityInvalid(availability.totalSize)) {
|
||||
setTotalSizeError(
|
||||
"You cannot allocate more space than the remaining space."
|
||||
);
|
||||
} else {
|
||||
setTotalSizeError("");
|
||||
}
|
||||
}, [availability, isAvailabilityInvalid]);
|
||||
|
||||
const onTotalSizeUnitChange = async (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
const element = e.currentTarget;
|
||||
const max = availabilityMax(space, availability.totalSizeUnit);
|
||||
const isValid = isAvailabilityValid(availability.totalSize, max);
|
||||
|
||||
dispatch({
|
||||
type: "toggle-next",
|
||||
isNextEnable: false,
|
||||
isNextEnable: isValid,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: "toggle-back",
|
||||
isBackEnable: true,
|
||||
});
|
||||
}, [space, availability]);
|
||||
|
||||
const onTotalSizeUnitChange = async (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
const element = e.currentTarget;
|
||||
|
||||
onAvailabilityChange({
|
||||
totalSize: 0,
|
||||
totalSizeUnit: element.value as "tb" | "gb",
|
||||
@ -72,29 +48,11 @@ export function AvailabilityForm({
|
||||
const onAvailablityChange = async (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const element = e.currentTarget;
|
||||
const v = element.value;
|
||||
const value = parseFloat(v);
|
||||
const valid = element.checkValidity() && !isAvailabilityInvalid(value);
|
||||
const valid = element.checkValidity();
|
||||
|
||||
onAvailabilityChange({
|
||||
[element.name]: v,
|
||||
});
|
||||
|
||||
if (valid) {
|
||||
setTotalSizeError("");
|
||||
dispatch({
|
||||
type: "toggle-next",
|
||||
isNextEnable: true,
|
||||
});
|
||||
} else {
|
||||
setTotalSizeError(
|
||||
element.validationMessage ||
|
||||
"You cannot allocate more space than the remaining space."
|
||||
);
|
||||
dispatch({
|
||||
type: "toggle-next",
|
||||
isNextEnable: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onInputChange = async (e: ChangeEvent<HTMLInputElement>) => {
|
||||
@ -105,11 +63,11 @@ export function AvailabilityForm({
|
||||
});
|
||||
};
|
||||
|
||||
const unit = availability.totalSizeUnit === "gb" ? GB : TB;
|
||||
const max = (
|
||||
space.quotaMaxBytes / unit -
|
||||
space.quotaReservedBytes / unit
|
||||
).toFixed(2);
|
||||
const max = availabilityMax(space, availability.totalSizeUnit);
|
||||
const isValid = isAvailabilityValid(availability.totalSize, max);
|
||||
const helper = isValid
|
||||
? "Total size of availability's storage in bytes"
|
||||
: "The total size cannot exceed the space available.";
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -120,16 +78,14 @@ export function AvailabilityForm({
|
||||
name="totalSize"
|
||||
type="number"
|
||||
label="Total size"
|
||||
helper={
|
||||
totalSizeError || "Total size of availability's storage in bytes"
|
||||
}
|
||||
helper={helper}
|
||||
className={classnames(
|
||||
["availabilityForm-item"],
|
||||
["availabilityForm-item--error", !!totalSizeError]
|
||||
["availabilityForm-item--error", !isValid]
|
||||
)}
|
||||
inputClassName="availabilityForm-itemInput"
|
||||
min={0.01}
|
||||
max={max}
|
||||
max={max.toFixed(2)}
|
||||
onChange={onAvailablityChange}
|
||||
onGroupChange={onTotalSizeUnitChange}
|
||||
value={availability.totalSize.toString()}
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
import { CodexNodeSpace } from "@codex-storage/sdk-js";
|
||||
import { UIAvailability } from "./types";
|
||||
import { AvailabilityState } from "./types";
|
||||
import { GB, TB } from "../../utils/constants";
|
||||
import { SpaceAllocation } from "@codex-storage/marketplace-ui-components";
|
||||
import "./AvailabilitySpaceAllocation.css";
|
||||
import { availabilityUnit } from "./availability.domain";
|
||||
|
||||
type Props = {
|
||||
space: CodexNodeSpace;
|
||||
availability: UIAvailability;
|
||||
availability: AvailabilityState;
|
||||
};
|
||||
|
||||
export function AvailabilitySpaceAllocation({ availability, space }: Props) {
|
||||
const unit = availability.totalSizeUnit === "gb" ? GB : TB;
|
||||
const unit = availabilityUnit(availability.totalSizeUnit);
|
||||
const { quotaMaxBytes, quotaReservedBytes } = space;
|
||||
const size = availability.totalSize * unit;
|
||||
const isUpdating = !!availability.id;
|
||||
|
||||
@ -1,19 +1,18 @@
|
||||
import {
|
||||
Placeholder,
|
||||
StepperAction,
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import { Dispatch, useEffect } from "react";
|
||||
import { Placeholder } from "@codex-storage/marketplace-ui-components";
|
||||
import { SuccessIcon } from "../SuccessIcon/SuccessIcon";
|
||||
import { AvailabilityComponentProps } from "./types";
|
||||
import { useEffect } from "react";
|
||||
|
||||
type Props = {
|
||||
dispatch: Dispatch<StepperAction>;
|
||||
};
|
||||
|
||||
export function AvailabilitySuccess({ dispatch }: Props) {
|
||||
export function AvailabilitySuccess({ dispatch }: AvailabilityComponentProps) {
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
isNextEnable: true,
|
||||
type: "toggle-next",
|
||||
isNextEnable: true,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: "toggle-back",
|
||||
isBackEnable: false,
|
||||
});
|
||||
}, [dispatch]);
|
||||
|
||||
|
||||
20
src/components/Availability/availability.domain.ts
Normal file
20
src/components/Availability/availability.domain.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { CodexNodeSpace } from "@codex-storage/sdk-js";
|
||||
import { GB, TB } from "../../utils/constants";
|
||||
import { AvailabilityState } from "./types";
|
||||
|
||||
export const availabilityUnit = (unit: "gb" | "tb") =>
|
||||
unit === "gb" ? GB : TB;
|
||||
|
||||
export const availabilityMax = (space: CodexNodeSpace, unit: "gb" | "tb") => {
|
||||
const bytes = availabilityUnit(unit);
|
||||
return space.quotaMaxBytes / bytes - space.quotaReservedBytes / bytes;
|
||||
};
|
||||
|
||||
export const isAvailabilityValid = (
|
||||
totalSize: string | number,
|
||||
max: number
|
||||
) => {
|
||||
const size = parseFloat(totalSize.toString());
|
||||
|
||||
return size > 0 && size <= max;
|
||||
};
|
||||
@ -1,4 +1,11 @@
|
||||
export type UIAvailability = {
|
||||
import {
|
||||
StepperAction,
|
||||
StepperState,
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import { CodexNodeSpace } from "@codex-storage/sdk-js";
|
||||
import { Dispatch } from "react";
|
||||
|
||||
export type AvailabilityState = {
|
||||
id?: string;
|
||||
totalSize: number;
|
||||
duration: number;
|
||||
@ -7,3 +14,12 @@ export type UIAvailability = {
|
||||
maxCollateral: number;
|
||||
totalSizeUnit: "gb" | "tb";
|
||||
};
|
||||
|
||||
export type AvailabilityComponentProps = {
|
||||
dispatch: Dispatch<StepperAction>;
|
||||
state: StepperState;
|
||||
space: CodexNodeSpace;
|
||||
onAvailabilityChange: (data: Partial<AvailabilityState>) => void;
|
||||
availability: AvailabilityState;
|
||||
error: Error | null;
|
||||
};
|
||||
|
||||
@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { GB, TB } from "../../utils/constants";
|
||||
import { Promises } from "../../utils/promises";
|
||||
import { WebStorage } from "../../utils/web-storage";
|
||||
import { UIAvailability } from "./types";
|
||||
import { AvailabilityState } from "./types";
|
||||
import { Dispatch, useState } from "react";
|
||||
import {
|
||||
StepperAction,
|
||||
@ -28,13 +28,13 @@ export function useAvailabilityMutation(
|
||||
duration,
|
||||
durationUnit = "days",
|
||||
...input
|
||||
}: UIAvailability) => {
|
||||
}: AvailabilityState) => {
|
||||
const unit = totalSizeUnit === "gb" ? GB : TB;
|
||||
const marketplace = CodexSdk.marketplace;
|
||||
const time = Times.toSeconds(duration, durationUnit);
|
||||
|
||||
const fn: (
|
||||
input: Omit<UIAvailability, "totalSizeUnit" | "durationUnit">
|
||||
input: Omit<AvailabilityState, "totalSizeUnit" | "durationUnit">
|
||||
) => Promise<SafeValue<unknown>> = input.id
|
||||
? (input) =>
|
||||
marketplace.updateAvailability({ ...input, id: input.id || "" })
|
||||
@ -70,12 +70,6 @@ export function useAvailabilityMutation(
|
||||
dispatch({
|
||||
type: "next",
|
||||
step: state.step,
|
||||
isBackEnable: true,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: "toggle-next",
|
||||
isNextEnable: false,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@ -8,10 +8,7 @@ import { Promises } from "../../utils/promises";
|
||||
import { AvailabilityCreate } from "../Availability/AvailabilityCreate";
|
||||
import { CodexSdk } from "../../sdk/codex";
|
||||
import { Strings } from "../../utils/strings";
|
||||
import { CodexAvailability } from "@codex-storage/sdk-js";
|
||||
import { useState } from "react";
|
||||
import { AvailabilitiesTable } from "../Availability/AvailabilitiesTable";
|
||||
import { AvailabilityReservations } from "../Availability/AvailabilityReservations";
|
||||
import "./Availabilities.css";
|
||||
|
||||
const defaultSpace = {
|
||||
|
||||
@ -13,8 +13,6 @@ export function ErrorPlaceholder({ subtitle, error }: Props) {
|
||||
error.message
|
||||
: `${error}`;
|
||||
|
||||
console.info(message, error);
|
||||
|
||||
return (
|
||||
<Placeholder
|
||||
Icon={<ErrorIcon />}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user