From 508977120db712dca3cd84ccf3f89235fd3a438b Mon Sep 17 00:00:00 2001 From: Arnaud Date: Fri, 20 Sep 2024 19:40:44 +0200 Subject: [PATCH] Create availability --- package-lock.json | 28 +-- package.json | 6 +- .../Availability/AvailabilityConfirm.css | 5 + .../Availability/AvailabilityConfirmation.tsx | 60 ++++++ .../Availability/AvailabilityCreate.tsx | 132 ++++++++++++ .../Availability/AvailabilityDone.css | 7 + .../Availability/AvailabilityDone.tsx | 28 +++ .../Availability/AvailabilityForm.css | 19 ++ .../Availability/AvailabilityForm.tsx | 197 ++++++++++++++++++ src/components/Availability/types.tsx | 9 + .../Availability/useAvailabilityMutation.ts | 76 +++++++ .../NodeSpaceAllocation.tsx | 3 - src/routes/dashboard/availabilities.tsx | 40 +++- src/sdk/codex.ts | 13 +- src/utils/constants.ts | 4 + src/utils/times.ts | 18 ++ 16 files changed, 615 insertions(+), 30 deletions(-) create mode 100644 src/components/Availability/AvailabilityConfirm.css create mode 100644 src/components/Availability/AvailabilityConfirmation.tsx create mode 100644 src/components/Availability/AvailabilityCreate.tsx create mode 100644 src/components/Availability/AvailabilityDone.css create mode 100644 src/components/Availability/AvailabilityDone.tsx create mode 100644 src/components/Availability/AvailabilityForm.css create mode 100644 src/components/Availability/AvailabilityForm.tsx create mode 100644 src/components/Availability/types.tsx create mode 100644 src/components/Availability/useAvailabilityMutation.ts create mode 100644 src/utils/times.ts diff --git a/package-lock.json b/package-lock.json index 09743fe..502d36b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 070c515..3c5c2db 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/components/Availability/AvailabilityConfirm.css b/src/components/Availability/AvailabilityConfirm.css new file mode 100644 index 0000000..f8cef42 --- /dev/null +++ b/src/components/Availability/AvailabilityConfirm.css @@ -0,0 +1,5 @@ +.availabilitConfirm-title { + margin-left: auto; + margin-right: auto; + margin-bottom: 1rem; +} diff --git a/src/components/Availability/AvailabilityConfirmation.tsx b/src/components/Availability/AvailabilityConfirmation.tsx new file mode 100644 index 0000000..fefd5b2 --- /dev/null +++ b/src/components/Availability/AvailabilityConfirmation.tsx @@ -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; + 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 ( + <> + Node space allocation + + + + ); +} diff --git a/src/components/Availability/AvailabilityCreate.tsx b/src/components/Availability/AvailabilityCreate.tsx new file mode 100644 index 0000000..110a511 --- /dev/null +++ b/src/components/Availability/AvailabilityCreate.tsx @@ -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({ + totalSize: 1, + duration: 1, + minPrice: 0, + maxCollateral: 0, + totalSizeUnit: "gb", + durationUnit: "days", + }); + + useEffect(() => { + Promise.all([ + WebStorage.get("availability-step"), + WebStorage.get("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, + 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] || (() => ); + const backLabel = state.step ? "Back" : "Close"; + const nextLabel = state.step === steps.current.length - 1 ? "Finish" : "Next"; + + return ( + <> +