Fix stepper steps

This commit is contained in:
Arnaud 2024-09-24 17:52:34 +02:00
parent 298a799e03
commit 655cbbbaa0
No known key found for this signature in database
GPG Key ID: 69D6CE281FCAE663
11 changed files with 155 additions and 159 deletions

View File

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

View File

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

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -13,8 +13,6 @@ export function ErrorPlaceholder({ subtitle, error }: Props) {
error.message
: `${error}`;
console.info(message, error);
return (
<Placeholder
Icon={<ErrorIcon />}