From d7f072ba66eede1ba7c1c9a5ac889e89fc5209b3 Mon Sep 17 00:00:00 2001 From: Arnaud Date: Fri, 13 Sep 2024 12:25:30 +0200 Subject: [PATCH] Improve the storage request component --- public/shape-1.png | Bin 0 -> 10602 bytes public/shape-2.png | Bin 0 -> 6156 bytes public/shape-3.png | Bin 0 -> 9354 bytes public/shape-4.png | Bin 0 -> 17510 bytes src/components/CardNumbers/CardNumbers.css | 22 +- src/components/CardNumbers/CardNumbers.tsx | 223 ++++++---- src/components/Range/Range.css | 140 +++++- src/components/Range/Range.tsx | 18 +- .../StorageRequestReview.css | 128 +++--- .../StorageRequestReview.tsx | 409 ++++++++++-------- .../StorageRequestSetup.css | 36 +- .../StorageRequestStepper.tsx | 25 +- src/routes/dashboard/purchases.tsx | 4 +- 13 files changed, 629 insertions(+), 376 deletions(-) create mode 100644 public/shape-1.png create mode 100644 public/shape-2.png create mode 100644 public/shape-3.png create mode 100644 public/shape-4.png diff --git a/public/shape-1.png b/public/shape-1.png new file mode 100644 index 0000000000000000000000000000000000000000..288284f1e03a0bf506f75b7e72d87845f1cfb165 GIT binary patch literal 10602 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 Ef&eaj(*OVf literal 0 HcmV?d00001 diff --git a/public/shape-2.png b/public/shape-2.png new file mode 100644 index 0000000000000000000000000000000000000000..52a303b6f41738c42f5037531c5dee5cb7593008 GIT binary patch literal 6156 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%0Af@_nX|OL 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 Eg31gUga7~l literal 0 HcmV?d00001 diff --git a/public/shape-4.png b/public/shape-4.png new file mode 100644 index 0000000000000000000000000000000000000000..89ec8082e4c28a04c04d1499f8078769a6b0abcb GIT binary patch literal 17510 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 B9qIr8 literal 0 HcmV?d00001 diff --git a/src/components/CardNumbers/CardNumbers.css b/src/components/CardNumbers/CardNumbers.css index a51730e..3230fde 100644 --- a/src/components/CardNumbers/CardNumbers.css +++ b/src/components/CardNumbers/CardNumbers.css @@ -8,11 +8,24 @@ flex-direction: column; } +.cardNumber-container { + display: flex; + flex-direction: column; +} + .cardNumber--error { border-color: rgb(var(--codex-color-error)); } -.cardNumber--error .cardNumber-tooltip { +.cardNumber-errorText, +.cardNumber-helperText { + height: 2rem; + display: inline-block; + margin-top: 0.25rem; + padding: 0 0.5rem; +} + +.cardNumber-errorText { color: rgb(var(--codex-color-error)); } @@ -64,6 +77,11 @@ .cardNumber .input { min-width: 0; - width: 100px; + width: 65px; height: 2.5rem; } + +.cardNumber .inputGroup-select { + height: 2.5rem; + padding: 0.25rem 1rem; +} diff --git a/src/components/CardNumbers/CardNumbers.tsx b/src/components/CardNumbers/CardNumbers.tsx index 05d69b4..13b9ab8 100644 --- a/src/components/CardNumbers/CardNumbers.tsx +++ b/src/components/CardNumbers/CardNumbers.tsx @@ -1,107 +1,178 @@ -import { ButtonIcon, Input, Tooltip } from "@codex-storage/marketplace-ui-components"; +import { + ButtonIcon, + SimpleText, +} from "@codex-storage/marketplace-ui-components"; import "./CardNumbers.css"; -import { Check, Info, Pencil, ShieldAlert } from "lucide-react"; -import { ChangeEvent, useEffect, useState } from "react"; +import { Check, CircleX, Pencil } from "lucide-react"; +import { ChangeEvent, useCallback, useEffect, useRef, useState } from "react"; import { classnames } from "../../utils/classnames"; type Props = { title: string; data: string; - comment?: string; - onChange?: (value: number) => void; - hasError?: boolean; + onChange: (value: string) => void; + onValidation?: (value: string) => string; + className?: string; + + /** + * If true, the caret will be set at the end of the input + * Default is true + */ + repositionCaret?: boolean; + + helper: string; }; export function CardNumbers({ title, data, - comment, - hasError = false, + onValidation, onChange, + helper, + className = "", + repositionCaret = true, }: Props) { - const [editing, setEditing] = useState(false); - const [value, setValue] = useState(data); + const [isDirty, setIsDirty] = useState(false); + const [error, setError] = useState(""); + const ref = useRef(null); + + const replaceCaret = useCallback( + (el: HTMLElement) => { + if (!repositionCaret) { + return; + } + + // Place the caret at the end of the element + const target = document.createTextNode(""); + el.appendChild(target); + // do not move caret if element was not focused + const isTargetFocused = document.activeElement === el; + if (target !== null && target.nodeValue !== null && isTargetFocused) { + const sel = window.getSelection(); + if (sel !== null) { + const range = document.createRange(); + range.setStart(target, target.nodeValue.length); + range.collapse(true); + sel.removeAllRanges(); + sel.addRange(range); + } + if (el instanceof HTMLElement) el.focus(); + } + }, + [repositionCaret] + ); + + const updateText = useCallback( + (text: string | null) => { + const current = ref.current; + + if (current && text) { + current.textContent = text; + replaceCaret(current); + } + }, + [replaceCaret, ref] + ); useEffect(() => { - setValue(data); - }, [data]); + console.info("received update //", data); + updateText(data); + setIsDirty(false); + }, [data, updateText]); - const onEditingClick = () => setEditing(!editing); + const onEditingClick = () => { + const current = ref.current; - const onInputChange = (e: ChangeEvent) => { - setValue(e.currentTarget.value); + if (isDirty) { + onChange?.(current?.textContent || ""); + } else if (current) { + current.focus(); + replaceCaret(current); + } }; - const onButtonClick = () => { - setEditing(false); - onChange?.(parseInt(value, 10)); + const onInput = (e: ChangeEvent) => { + const text = e.currentTarget.textContent; + + setIsDirty(text !== data); + + if (!text) { + setError("A value is required"); + return; + } + + if (text?.length > 10) { + e.currentTarget.textContent = text.slice(0, 10); + replaceCaret(e.currentTarget); + setError("The value is too long"); + return; + } + + updateText(text); + + const msg = onValidation?.(text); + + if (msg) { + setError(msg); + return; + } + + setError(""); }; - if (editing) { - return ( + const onBlur = () => { + if (error === "") { + if (isDirty) { + onChange?.(ref.current?.textContent || ""); + } + } else { + updateText(data); + } + + setIsDirty(false); + setError(""); + }; + + const Icon = error + ? () => + : isDirty + ? () => + : () => ; + + return ( +
+ className={classnames(["cardNumber"], ["cardNumber--error", !!error])}>
- - + <> +

+ + +

{title} - {comment && ( - - {hasError ? ( - - ) : ( - - )} - - )}
- ); - } - - const DataContainer = editing ? ( - <> - - - - ) : ( - <> -

{data}

- }> - - ); - - return ( -
-
{DataContainer}
-
- {title} - {comment && ( - - {hasError ? : } - - )} -
+ {error ? ( + {error} + ) : ( + + {helper} + + )}
); } diff --git a/src/components/Range/Range.css b/src/components/Range/Range.css index d860eb6..d9e9919 100644 --- a/src/components/Range/Range.css +++ b/src/components/Range/Range.css @@ -1,11 +1,145 @@ .range { - width: 100%; + /* width: 100%; accent-color: var(--codex-color-primary); - height: 4px; - outline: none; + height: 1px; + outline: none; */ + --val: 50; + width: 100%; + margin: 1.5rem 0; } .range-labels { display: flex; justify-content: space-between; } + +@property --c { + syntax: ""; + inherits: true; + initial-value: #0000; +} + +.glow { + --c: rgb(0, 255, 255, calc(0.25 + var(--val) / 125)); + --c: hsl(160deg 80% 50% / calc(0.25 + var(--val) / 125)); + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: transparent; + cursor: pointer; + position: relative; +} + +.glow::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: calc((var(--val) - 1) * 1%); + min-width: 0.5em; + height: 100%; + background: var(--c); + box-shadow: + 0 0 0.2em 0 hsl(0 0% 0%) inset, + -0.1em 0.1em 0.1em -0.1em hsl(0 0% 100% / 0.5), + 0 0 calc(1em + 0.001em * var(--val)) calc(0.1em + 0.00025em * var(--val)) + var(--c); + border-radius: 1em 0 0 1em; + aopacity: calc(20% + var(--val) * 1%); +} + +/***** Track Styles *****/ +/***** Chrome, Safari, Opera, and Edge Chromium *****/ +.glow::-webkit-slider-runnable-track { + box-shadow: + 0 0 0.2em 0 hsl(0 0% 0%) inset, + -0.1em 0.1em 0.1em -0.1em hsl(0 0% 100% / 0.5); + background: linear-gradient(to bottom right, #0001, #0000), #343133; + border-radius: 1em; + height: 1em; +} + +/******** Firefox ********/ +.glow::-moz-range-track { + box-shadow: + 0 0 2px 0 hsl(0 0% 0%) inset, + -1px 1px 1px -1px hsl(0 0% 100% / 0.5); + background: + linear-gradient(var(--c) 0 0) 0 0 / calc(var(--val) * 1%) 100% no-repeat, + linear-gradient(to bottom right, #0001, #0000), + #343133; + border-radius: 1em; + height: 1em; +} + +/***** Thumb Styles *****/ +/***** Chrome, Safari, Opera, and Edge Chromium *****/ +.glow::-webkit-slider-thumb { + --d: var(--c); + --d: rgb(from var(--c) r g b / calc(0.35 * var(--val) * 1%)); + -webkit-appearance: none; /* Override default look */ + appearance: none; + background-color: #5cd5eb; + transform: translateY(calc(-50% + 0.5em)); + width: 4em; + aspect-ratio: 1; + background: red; + border-radius: 50%; + background: + radial-gradient( + farthest-side, + #0000 22.5%, + var(--d) 0, + #0000 calc(var(--val) * 0.75%) + ) + 50% 50% / 100% 100% no-repeat, + radial-gradient(#0000 15%, #343133 16%, #545153 20%), + repeating-linear-gradient(#0000 0 10%, #0002 0 20%) 50% 50% / 25% 25% + no-repeat, + repeating-linear-gradient(90deg, #0000 0 10%, #0002 0 20%) 50% 50% / 25% 25% + no-repeat, + radial-gradient(var(--c) 17%, #0000 0), + #545153; + box-shadow: + inset -0.15em -0.15em 0.2em #0008, + inset 0.15em 0.15em 0.2em #ffffff22, + inset calc(var(--val) * 1em / 500) 0em calc(var(--val) * 1em / 500) + calc(var(--val) * -1em / 700) var(--c), + 0.25em 0.25em 0.5em #0006, + calc(0.0125em * var(--val)) calc(0.005em * var(--val)) + calc(0.02em * var(--val)) calc(-0.01em * var(--val)) #000a; + border-radius: 50%; +} + +/***** Firefox *****/ +.glow::-moz-range-thumb { + /* --d: var(--c); + --d: rgb(from var(--c) r g b / calc(0.35 * var(--val) * 1%)); */ + border: none; /*Removes extra border that FF applies*/ + -webkit-appearance: none; /* Override default look */ + appearance: none; + background-color: #5cd5eb; + width: 4em; + height: 4em; + aspect-ratio: 1; + background: red; + border-radius: 50%; + background: +/* radial-gradient(farthest-side, #0000 22.5%, var(--d) 0, #0000 calc(var(--val) * 0.75%)) 50% 50% / 100% 100% no-repeat, */ + radial-gradient(#0000 15%, #343133 16%, #545153 20%), + repeating-linear-gradient(#0000 0 10%, #0002 0 20%) 50% 50% / 25% 25% + no-repeat, + repeating-linear-gradient(90deg, #0000 0 10%, #0002 0 20%) 50% 50% / 25% 25% + no-repeat, + radial-gradient(var(--c) 17%, #0000 0), + #545153; + box-shadow: + inset -0.15em -0.15em 0.2em #0008, + inset 0.15em 0.15em 0.2em #ffffff22, + inset calc(var(--val) * 1em / 500) 0em calc(var(--val) * 1em / 500) + calc(var(--val) * -1em / 700) var(--c), + 0.25em 0.25em 0.5em #0006, + calc(0.015em * var(--val)) calc(0.005em * var(--val)) + calc(0.02em * var(--val)) calc(-0.01em * var(--val)) #0008; + border-radius: 50%; +} diff --git a/src/components/Range/Range.tsx b/src/components/Range/Range.tsx index 369bbcb..e94415d 100644 --- a/src/components/Range/Range.tsx +++ b/src/components/Range/Range.tsx @@ -1,4 +1,4 @@ -import { ChangeEvent } from "react"; +import { ChangeEvent, FormEvent, useState } from "react"; import "./Range.css"; type Props = { @@ -14,12 +14,17 @@ type Props = { export function Range({ label, max, - labels, onChange, defaultValue, value, className = "", }: Props) { + const [val, setVal] = useState(value); + + const onInput = (e: FormEvent) => { + setVal(parseInt(e.currentTarget.value, 10)); + }; + return (
{label} @@ -27,19 +32,20 @@ export function Range({ type="range" max={max} min={1} - step="1" - className="range" + className="range glow" onChange={onChange} defaultValue={defaultValue} value={value} + style={{ "--val": val } as React.CSSProperties} + onInput={onInput} /> -
+ {/*
{labels.map((l) => (
{l}
))} -
+
*/}
); } diff --git a/src/components/StorageRequestSetup/StorageRequestReview.css b/src/components/StorageRequestSetup/StorageRequestReview.css index fe68a66..d242404 100644 --- a/src/components/StorageRequestSetup/StorageRequestReview.css +++ b/src/components/StorageRequestSetup/StorageRequestReview.css @@ -1,61 +1,6 @@ -.storageRequestReview-bar { - background-image: linear-gradient(to right, #ef4444, #facc15, #2dd4bf); - border-radius: var(--codex-border-radius); - height: 10px; - position: relative; - margin-top: 1rem; -} - -.storageRequestReview-barIndicator { - position: absolute; - border: 2px solid rgb(38 38 38); - background-color: rgb(249 115 22); - height: 1.25rem; - width: 0.5rem; - top: -6px; - bottom: 0; - transform: translateX(270px); -} - -.storageRequestReview-legendItem, -.storageRequestReview-legend { - display: flex; - align-items: center; - gap: 0.75rem; -} - -.storageRequestReview-legend { - justify-content: space-between; - margin-bottom: 0.25rem; - margin-top: 0.75rem; -} - -.storageRequestReview-legendItemColor { - border-radius: 2px; - height: 10px; - width: 10px; - display: inline-block; - background-color: var(--codex-storage-request-review-legend-item-color); -} - -.storageRequestReview-legendItemColor-cheap { - --codex-storage-request-review-legend-item-color: rgb(239 68 68); -} - -.storageRequestReview-legendItemColor-average { - --codex-storage-request-review-legend-item-color: rgb(249 115 22); -} - -.storageRequestReview-legendItemColor-good { - --codex-storage-request-review-legend-item-color: rgb(254 240 138); -} - -.storageRequestReview-legendItemColor-excellent { - --codex-storage-request-review-legend-item-color: rgb(45 212 191); -} - .storageRequestReview-hr { - margin: 1.5rem 0; + margin-bottom: 1.5rem; + margin-top: 0rem; } .storageRequestReview-numbers { @@ -75,20 +20,71 @@ .storageRequestReview-range--disabled .range { opacity: 0.5; } -/* -.storageRequestReview-range--disabled .range::-webkit-slider-thumb { - background-color: var(--codex-background-light); -} */ + +.storageRequestReview-presets { + display: flex; + padding: 0 0.5rem 2rem 0.5rem; + gap: 1rem; +} + +.storageRequestReview-presets-blocs { + display: flex; + flex: 1; + gap: 0.5rem; +} + +.storageRequestReview-presets-bloc { + flex: 1; + border-radius: var(--codex-border-radius); + background-color: rgb(56 56 56); + align-items: center; + padding: 1rem 2rem; + align-items: center; + justify-content: center; + text-align: center; + display: flex; + flex-direction: column; + gap: 0.5rem; + transition: opacity 0.35s; + cursor: pointer; + border: 1px solid transparent; +} + +.storageRequest-price { + display: flex; + justify-content: center; +} + +.storageRequestReview-presets-title { + display: flex; + flex-direction: column; + justify-content: center; +} + +.storageRequestReview-presets-bloc:not( + .storageRequestReview-presets--selected + ):hover { + border: 1px solid var(--codex-border-color); +} + +.storageRequestReview-presets--selected { + border: 1px solid var(--codex-color-primary); +} + +.storageRequestReview-alert { + display: flex; + gap: 1rem; + align-items: flex-start; +} + +.storageRequestReview-expiration { + min-width: 33%; +} @media (max-width: 800px) { .storageRequestReview-numbers { grid-template-columns: 1fr 1fr; } - - .storageRequestReview-legend { - flex-direction: column; - align-items: flex-start; - } } @media (min-width: 801px) { diff --git a/src/components/StorageRequestSetup/StorageRequestReview.tsx b/src/components/StorageRequestSetup/StorageRequestReview.tsx index d3a40a7..9cabc18 100644 --- a/src/components/StorageRequestSetup/StorageRequestReview.tsx +++ b/src/components/StorageRequestSetup/StorageRequestReview.tsx @@ -1,15 +1,11 @@ -import { ChangeEvent, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { WebStorage } from "../../utils/web-storage"; import "./StorageRequestReview.css"; import { Alert } from "@codex-storage/marketplace-ui-components"; import { CardNumbers } from "../CardNumbers/CardNumbers"; -import { Range } from "../Range/Range"; import { FileWarning } from "lucide-react"; import { classnames } from "../../utils/classnames"; -const plurals = (type: "node" | "token" | "second" | "minute", value: number) => - `${value} ${type}` + (value > 1 ? "s" : ""); - type Props = { onChangeNextState: (value: "enable" | "disable") => void; }; @@ -39,31 +35,9 @@ type Durability = { }; const durabilities = [ - { nodes: 2, tolerance: 0, proofProbability: 1 }, { nodes: 3, tolerance: 1, proofProbability: 2 }, { nodes: 4, tolerance: 2, proofProbability: 3 }, - { nodes: 5, tolerance: 3, proofProbability: 4 }, - { nodes: 6, tolerance: 4, proofProbability: 5 }, -]; - -type Price = { - reward: number; - collateral: number; -}; - -const prices = [ - { - reward: 5, - collateral: 5, - }, - { - reward: 10, - collateral: 10, - }, - { - reward: 50, - collateral: 20, - }, + { nodes: 5, tolerance: 2, proofProbability: 4 }, ]; const findDurabilityIndex = (d: Durability) => { @@ -76,24 +50,11 @@ const findDurabilityIndex = (d: Durability) => { return durabilities.findIndex((d) => JSON.stringify(d) === s); }; -const findPriceIndex = (d: Price) => { - const s = JSON.stringify({ - reward: d.reward, - collateral: d.collateral, - }); - - return prices.findIndex((p) => JSON.stringify(p) === s); -}; +const units = ["days", "minutes", "hours", "days", "months", "years"]; export function StorageRequestReview({ onChangeNextState }: Props) { const [cid, setCid] = useState(""); - const [errors, setErrors] = useState({ - nodes: "", - tolerance: "", - proofProbability: "", - }); const [durability, setDurability] = useState(1); - const [price, setPrice] = useState(1); const [data, setData] = useState({ availabilityUnit: "days", availability: 1, @@ -120,13 +81,6 @@ export function StorageRequestReview({ onChangeNextState }: Props) { }); setDurability(index + 1); - - const pindex = findPriceIndex({ - reward: d.reward, - collateral: d.collateral, - }); - - setPrice(pindex + 1); } else { WebStorage.set("storage-request-criteria", { availabilityUnit: "days", @@ -158,44 +112,86 @@ export function StorageRequestReview({ onChangeNextState }: Props) { }); }; - const onDurabilityRangeChange = (e: ChangeEvent) => { - const l = parseInt(e.currentTarget.value, 10); + const onDurabilityChange = (d: number) => { + const durability = durabilities[d - 1]; - const durability = durabilities[l - 1]; - - updateData(durability); - setDurability(l); - setErrors({ nodes: "", tolerance: "", proofProbability: "" }); + if (durability) { + updateData(durability); + setDurability(d); + } else { + setDurability(0); + } }; - const onPriceRangeChange = (e: ChangeEvent) => { - const l = parseInt(e.currentTarget.value, 10); - - const price = prices[l - 1]; - - updateData(price); - setPrice(l); - }; - - const isUnvalidConstrainst = (nodes: number, tolerance: number) => { + const isInvalidConstrainst = (nodes: number, tolerance: number) => { const ecK = nodes - tolerance; const ecM = tolerance; return ecK <= 1 || ecK < ecM; }; - const onNodesChange = (nodes: number) => { - setErrors((e) => ({ ...e, tolerance: "" })); + const isInvalidNodes = (nodes: string) => { + const error = isInvalidNumber(nodes); - if (isUnvalidConstrainst(nodes, data.tolerance)) { - setErrors((e) => ({ - ...e, - nodes: - "The data does not match Codex contrainst. Try with other values.", - })); - return; + if (error) { + return error; } + const n = Number(nodes); + + if (isInvalidConstrainst(n, data.tolerance)) { + return "The data does not match Codex contrainst"; + } + + return ""; + }; + + const isInvalidTolerance = (tolerance: string) => { + const error = isInvalidNumber(tolerance); + + if (error) { + return error; + } + + const n = Number(tolerance); + + if (n > data.nodes) { + return "The tolerance cannot be greater than the nodes."; + } + + if (isInvalidConstrainst(data.nodes, n)) { + return "The data does not match Codex contrainst."; + } + + return ""; + }; + + const isInvalidAvailability = (data: string) => { + const [value, unit = "days"] = data.split(" "); + + const error = isInvalidNumber(value); + + if (error) { + return error; + } + + // if (!unit.endsWith("s")) { + // unit += "s"; + // } + + if (!units.includes(unit)) { + return "Invalid unit must one of: minutes, hours, days, months, years"; + } + + return ""; + }; + + const isInvalidNumber = (value: string) => + isNaN(Number(value)) ? "The value is not a number" : ""; + + const onNodesChange = (value: string) => { + const nodes = Number(value); + updateData({ nodes }); const index = findDurabilityIndex({ @@ -207,25 +203,8 @@ export function StorageRequestReview({ onChangeNextState }: Props) { setDurability(index + 1); }; - const onToleranceChange = (tolerance: number) => { - setErrors((e) => ({ ...e, tolerance: "" })); - - if (tolerance > data.nodes) { - setErrors((e) => ({ - ...e, - tolerance: "The tolerance cannot be greater than the nodes.", - })); - return; - } - - if (isUnvalidConstrainst(data.nodes, tolerance)) { - setErrors((e) => ({ - ...e, - tolerance: - "The data does not match Codex contrainst. Try with other values.", - })); - return; - } + const onToleranceChange = (value: string) => { + const tolerance = Number(value); updateData({ tolerance }); @@ -238,7 +217,9 @@ export function StorageRequestReview({ onChangeNextState }: Props) { setDurability(index + 1); }; - const onProofProbabilityChange = (proofProbability: number) => { + const onProofProbabilityChange = (value: string) => { + const proofProbability = Number(value); + updateData({ proofProbability }); const index = findDurabilityIndex({ @@ -250,58 +231,134 @@ export function StorageRequestReview({ onChangeNextState }: Props) { setDurability(index + 1); }; - const onAvailabilityChange = (availability: number) => - updateData({ availability }); + const onAvailabilityChange = (value: string) => { + const [availability, availabilityUnit = "days"] = value.split(" "); + + // if (!availabilityUnit.endsWith("s")) { + // availabilityUnit += "s"; + // } + + updateData({ + availability: Number(availability), + availabilityUnit: availabilityUnit as AvailabilityUnit, + }); + }; + + const onRewardChange = (value: string) => { + const reward = Number(value); - const onRewardChange = (reward: number) => { updateData({ reward }); - - const index = findPriceIndex({ - reward, - collateral: data.collateral, - }); - - setPrice(index + 1); }; - const onCollateralChange = (collateral: number) => { + const onExpirationChange = (value: string) => { + const expiration = Number(value); + + updateData({ expiration }); + }; + + const onCollateralChange = (value: string) => { + const collateral = Number(value); + updateData({ collateral }); - - const index = findPriceIndex({ - collateral, - reward: data.reward, - }); - - setPrice(index + 1); }; + // const pluralizeUnit = () => { + // if (data.availability > 1 && !data.availabilityUnit.endsWith("s")) { + // return data.availability + " " +data.availabilityUnit + "s"; + // } + + // if (data.availability <= 1 && data.availabilityUnit.endsWith("s")) { + // return data.availabilityUnit.slice(0, -1); + // } + + // return data.availabilityUnit; + // }; + + const availability = `${data.availability} ${data.availabilityUnit}`; + return (
- Choose your criteria + Durability
+ onValidation={isInvalidNodes} + helper="Number of storage nodes"> + onValidation={isInvalidTolerance} + helper="Failure node tolerated"> + helper="Proof request frequency in seconds">
- +
+ Define your durability profile +

+ Select the appropriate level of data storage reliability ensuring + your information is protected and accessible. +

+
+
+
onDurabilityChange(0)} + className={classnames( + ["storageRequestReview-presets-bloc"], + [ + "storageRequestReview-presets--selected", + durability <= 0 || durability > 3, + ] + )}> +
+ +
+

Custom

+
+
onDurabilityChange(1)} + className={classnames( + ["storageRequestReview-presets-bloc"], + ["storageRequestReview-presets--selected", durability === 1] + )}> +
+ +
+

Low

+
+
onDurabilityChange(2)} + className={classnames( + ["storageRequestReview-presets-bloc"], + ["storageRequestReview-presets--selected", durability === 2] + )}> +
+ +
+

Medium

+
+
onDurabilityChange(3)} + className={classnames( + ["storageRequestReview-presets-bloc"], + ["storageRequestReview-presets--selected", durability === 3] + )}> +
+ +
+

High

+
+
+
+ + {/* + /> */} + + Commitment
- + data={availability} + onChange={onAvailabilityChange} + onValidation={isInvalidAvailability} + repositionCaret={false} + helper="Full period of the contract"> + - - + onChange={onRewardChange} + onValidation={isInvalidNumber} + helper="Penality tokens"> +
+ {/* */}
- - } - title="Warning" - variant="warning" - className="storageRequestReview-alert"> - This request with CID - {cid} will expire in - {plurals("minute", data.expiration)} - after the start.
- If no suitable hosts are found matching your storage requirements, you - will incur a charge of X tokens. -

-

- - Price comparaison with the market - -

-
-
- - Cheap -
-
- - Average -
- -
- - Good -
- -
- - Excellent -
-
-
-
+
+ + } + title="Warning" + variant="warning" + className="storageRequestReview-alert"> + If no suitable hosts are found for the CID {cid} matching your storage + requirements, you will incur a charge a small amount of tokens. +
); diff --git a/src/components/StorageRequestSetup/StorageRequestSetup.css b/src/components/StorageRequestSetup/StorageRequestSetup.css index 3a62156..d09e908 100644 --- a/src/components/StorageRequestSetup/StorageRequestSetup.css +++ b/src/components/StorageRequestSetup/StorageRequestSetup.css @@ -8,6 +8,15 @@ overflow-x: hidden; opacity: 0; z-index: -1; + max-height: 100%; + left: 50%; + top: 50%; + transform: translate(-50%, -50%) scale(0); + position: fixed; +} + +.storageRequest-open { + transform: translate(-50%, -50%) scale(1); } .storageRequest-open { @@ -80,23 +89,9 @@ @media (max-width: 800px) { .storageRequest { - margin: auto; - width: 100%; - position: absolute; - top: 0; - left: 0; - min-height: 100%; - display: flex; - align-items: center; - - .alert { - flex-direction: column; - align-items: flex-start; - } - .stepper-body, .stepper { - width: calc(100% - 3rem); + /* width: calc(100% - 3rem); */ } } } @@ -106,15 +101,4 @@ margin: auto; width: 85%; } - - .storageRequest { - left: 50%; - top: 50%; - transform: translate(-50%, -50%) scale(0); - position: fixed; - } - - .storageRequest-open { - transform: translate(-50%, -50%) scale(1); - } } diff --git a/src/components/StorageRequestSetup/StorageRequestStepper.tsx b/src/components/StorageRequestSetup/StorageRequestStepper.tsx index b917a88..984ac67 100644 --- a/src/components/StorageRequestSetup/StorageRequestStepper.tsx +++ b/src/components/StorageRequestSetup/StorageRequestStepper.tsx @@ -29,7 +29,7 @@ function calculateAvailability(value: number, unit: StorageAvailabilityUnit) { case "months": return 30 * 24 * 60 * 60 * value; case "years": - return 365 * 30 * 60 * 60 * value; + return 365 * 24 * 60 * 60 * value; } } @@ -50,7 +50,7 @@ export function StorageRequestStepper({ className, open, onClose }: Props) { message: "", }); - const { mutateAsync, isPending } = useMutation({ + const { mutateAsync } = useMutation({ mutationKey: ["debug"], mutationFn: (input: CodexCreateStorageRequestInput) => CodexSdk.marketplace() @@ -94,9 +94,6 @@ export function StorageRequestStepper({ className, open, onClose }: Props) { const components = [ StorageRequestFileChooser, - // StorageRequestAvailability, - // StorageRequestDurability, - // StorageRequestPrice, StorageRequestReview, StorageRequestDone, ]; @@ -111,20 +108,27 @@ export function StorageRequestStepper({ className, open, onClose }: Props) { if (state === "before") { setProgress(true); + setStep(s); return; } if (s >= steps.current.length) { + // TODO remove this + // Just a workaround because the request could take some time + // but the current client is doing the job in the main thread. + // So we are just waiting that the request is done for now. + await new Promise((resolve) => setTimeout(resolve, 3000)); + setIsNextDisable(true); - setProgress(false); if (s >= steps.current.length) { - console.info("delete"); setStep(0); WebStorage.delete("storage-request-step"); WebStorage.delete("storage-request-criteria"); } + setProgress(false); + onClose(); return; @@ -134,7 +138,6 @@ export function StorageRequestStepper({ className, open, onClose }: Props) { setIsNextDisable(true); setProgress(false); - setStep(s); if (s == 2) { setIsNextDisable(true); @@ -172,12 +175,14 @@ export function StorageRequestStepper({ className, open, onClose }: Props) { tolerance, reward, }); + + // TODO next step } else { setIsNextDisable(false); } }; - const Body = components[step] || components[0]; + const Body = progress ? () => : components[step] || components[0]; return ( <> @@ -193,7 +198,7 @@ export function StorageRequestStepper({ className, open, onClose }: Props) { Body={} step={step} onChangeStep={onChangeStep} - progress={progress || isPending} + progress={progress} isNextDisable={progress || isNextDisable}>
diff --git a/src/routes/dashboard/purchases.tsx b/src/routes/dashboard/purchases.tsx index 3a432ac..7242609 100644 --- a/src/routes/dashboard/purchases.tsx +++ b/src/routes/dashboard/purchases.tsx @@ -68,7 +68,7 @@ const Purchases = () => { , , , - , + , , ]; }) || []; @@ -93,7 +93,7 @@ const Purchases = () => { onClose={() => setOpen(false)} /> - {!open && } +
{/* {!cells.length && (