From 655cbbbaa0f6f20d0640fe4b446d4092d2daffcc Mon Sep 17 00:00:00 2001 From: Arnaud Date: Tue, 24 Sep 2024 17:52:34 +0200 Subject: [PATCH] Fix stepper steps --- .../Availability/AvailabilityConfirmation.tsx | 32 +++---- .../Availability/AvailabilityCreate.tsx | 76 +++++++-------- .../Availability/AvailabilityError.tsx | 27 ++++++ .../Availability/AvailabilityForm.tsx | 96 +++++-------------- .../AvailabilitySpaceAllocation.tsx | 7 +- .../Availability/AvailabilitySuccess.tsx | 21 ++-- .../Availability/availability.domain.ts | 20 ++++ src/components/Availability/types.tsx | 18 +++- .../Availability/useAvailabilityMutation.ts | 12 +-- .../Availailibities/Availabilities.tsx | 3 - .../ErrorPlaceholder/ErrorPlaceholder.tsx | 2 - 11 files changed, 155 insertions(+), 159 deletions(-) create mode 100644 src/components/Availability/AvailabilityError.tsx create mode 100644 src/components/Availability/availability.domain.ts diff --git a/src/components/Availability/AvailabilityConfirmation.tsx b/src/components/Availability/AvailabilityConfirmation.tsx index a80d130..1b5ed4b 100644 --- a/src/components/Availability/AvailabilityConfirmation.tsx +++ b/src/components/Availability/AvailabilityConfirmation.tsx @@ -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; - 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 ( <> diff --git a/src/components/Availability/AvailabilityCreate.tsx b/src/components/Availability/AvailabilityCreate.tsx index 56494a3..47e69d4 100644 --- a/src/components/Availability/AvailabilityCreate.tsx +++ b/src/components/Availability/AvailabilityCreate.tsx @@ -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({ - totalSize: 1, - duration: 1, - minPrice: 0, - maxCollateral: 0, - totalSizeUnit: "gb", - durationUnit: "days", - }); - const { state, dispatch } = useStepperReducer(STEPS); + const [availability, setAvailability] = useState( + defaultAvailabilityData + ); + const { state, dispatch } = useStepperReducer(); const { mutateAsync, error } = useAvailabilityMutation(dispatch, state); - const components = [ - AvailabilityForm, - AvailabilityConfirm, - error - ? () => ( - - ) - : AvailabilitySuccess, - ]; useEffect(() => { Promise.all([ WebStorage.get("availability-step"), - WebStorage.get("availability"), + WebStorage.get("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) => { + const onAvailabilityChange = (data: Partial) => { 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} /> diff --git a/src/components/Availability/AvailabilityError.tsx b/src/components/Availability/AvailabilityError.tsx new file mode 100644 index 0000000..e993590 --- /dev/null +++ b/src/components/Availability/AvailabilityError.tsx @@ -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 ( + + ); +} diff --git a/src/components/Availability/AvailabilityForm.tsx b/src/components/Availability/AvailabilityForm.tsx index 673e5fb..c7c09dc 100644 --- a/src/components/Availability/AvailabilityForm.tsx +++ b/src/components/Availability/AvailabilityForm.tsx @@ -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; - state: StepperState; - space: CodexNodeSpace; - onAvailabilityChange: (data: Partial) => 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) => { - 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) => { + 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) => { 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) => { @@ -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()} diff --git a/src/components/Availability/AvailabilitySpaceAllocation.tsx b/src/components/Availability/AvailabilitySpaceAllocation.tsx index 3e49899..86cf24e 100644 --- a/src/components/Availability/AvailabilitySpaceAllocation.tsx +++ b/src/components/Availability/AvailabilitySpaceAllocation.tsx @@ -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; diff --git a/src/components/Availability/AvailabilitySuccess.tsx b/src/components/Availability/AvailabilitySuccess.tsx index 70ed625..5ce0d1c 100644 --- a/src/components/Availability/AvailabilitySuccess.tsx +++ b/src/components/Availability/AvailabilitySuccess.tsx @@ -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; -}; - -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]); diff --git a/src/components/Availability/availability.domain.ts b/src/components/Availability/availability.domain.ts new file mode 100644 index 0000000..3175fa5 --- /dev/null +++ b/src/components/Availability/availability.domain.ts @@ -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; +}; diff --git a/src/components/Availability/types.tsx b/src/components/Availability/types.tsx index 9f61d4d..c2f7f3c 100644 --- a/src/components/Availability/types.tsx +++ b/src/components/Availability/types.tsx @@ -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; + state: StepperState; + space: CodexNodeSpace; + onAvailabilityChange: (data: Partial) => void; + availability: AvailabilityState; + error: Error | null; +}; diff --git a/src/components/Availability/useAvailabilityMutation.ts b/src/components/Availability/useAvailabilityMutation.ts index 21df44d..a2d74a3 100644 --- a/src/components/Availability/useAvailabilityMutation.ts +++ b/src/components/Availability/useAvailabilityMutation.ts @@ -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 + input: Omit ) => Promise> = 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, }); }, }); diff --git a/src/components/Availailibities/Availabilities.tsx b/src/components/Availailibities/Availabilities.tsx index d33b94b..a6921f9 100644 --- a/src/components/Availailibities/Availabilities.tsx +++ b/src/components/Availailibities/Availabilities.tsx @@ -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 = { diff --git a/src/components/ErrorPlaceholder/ErrorPlaceholder.tsx b/src/components/ErrorPlaceholder/ErrorPlaceholder.tsx index e7d28bb..79629ae 100644 --- a/src/components/ErrorPlaceholder/ErrorPlaceholder.tsx +++ b/src/components/ErrorPlaceholder/ErrorPlaceholder.tsx @@ -13,8 +13,6 @@ export function ErrorPlaceholder({ subtitle, error }: Props) { error.message : `${error}`; - console.info(message, error); - return ( }