Improve the storage request component

This commit is contained in:
Arnaud 2024-09-13 12:25:30 +02:00
parent b08ffeaa89
commit d7f072ba66
No known key found for this signature in database
GPG Key ID: 69D6CE281FCAE663
13 changed files with 629 additions and 376 deletions

BIN
public/shape-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
public/shape-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

BIN
public/shape-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
public/shape-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

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

View File

@ -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<HTMLParagraphElement>(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<HTMLInputElement>) => {
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<HTMLInputElement>) => {
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
? () => <CircleX size={"1rem"} />
: isDirty
? () => <Check size={"1rem"} />
: () => <Pencil size={"1rem"} />;
return (
<div className={classnames(["cardNumber-container"], [className])}>
<div
className={classnames(["cardNumber"], ["cardNumber--error", hasError])}>
className={classnames(["cardNumber"], ["cardNumber--error", !!error])}>
<div className="cardNumber-dataContainer">
<Input
id={title}
value={value}
onChange={onInputChange}
type="number"></Input>
<ButtonIcon
Icon={Check}
variant="small"
onClick={onButtonClick}></ButtonIcon>
<>
<p
ref={ref}
className="cardNumber-data"
onBlur={onBlur}
onInput={onInput}
contentEditable={true}
/>
<ButtonIcon
onClick={onEditingClick}
variant="small"
Icon={Icon}></ButtonIcon>
</>
</div>
<div className="cardNumber-info">
<b className="cardNumber-title">{title}</b>
{comment && (
<Tooltip message={comment} className="cardNumber-tooltip">
{hasError ? (
<ShieldAlert size={"1rem"} />
) : (
<Info size={"1rem"} />
)}
</Tooltip>
)}
</div>
</div>
);
}
const DataContainer = editing ? (
<>
<Input
id={title}
value={value}
onChange={onInputChange}
type="number"></Input>
<ButtonIcon
Icon={Check}
variant="small"
onClick={onButtonClick}></ButtonIcon>
</>
) : (
<>
<p className="cardNumber-data">{data}</p>
<ButtonIcon
onClick={onEditingClick}
variant="small"
Icon={() => <Pencil size={"1rem"} />}></ButtonIcon>
</>
);
return (
<div
className={classnames(["cardNumber"], ["cardNumber--error", hasError])}>
<div className="cardNumber-dataContainer">{DataContainer}</div>
<div className="cardNumber-info">
<b className="cardNumber-title">{title}</b>
{comment && (
<Tooltip message={comment} className="cardNumber-tooltip">
{hasError ? <ShieldAlert size={"1rem"} /> : <Info size={"1rem"} />}
</Tooltip>
)}
</div>
{error ? (
<small className="cardNumber-errorText">{error}</small>
) : (
<SimpleText
size="small"
variant="light"
className="cardNumber-helperText">
{helper}
</SimpleText>
)}
</div>
);
}

View File

@ -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: "<color>";
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%;
}

View File

@ -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<HTMLInputElement>) => {
setVal(parseInt(e.currentTarget.value, 10));
};
return (
<div className={className}>
{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}
/>
<div className="range-labels">
{/* <div className="range-labels">
{labels.map((l) => (
<div className="range-label" key={l}>
{l}
</div>
))}
</div>
</div> */}
</div>
);
}

View File

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

View File

@ -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<number>(1);
const [price, setPrice] = useState<number>(1);
const [data, setData] = useState<Data>({
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<HTMLInputElement>) => {
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<HTMLInputElement>) => {
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 (
<div>
<span className="storageRequest-title">Choose your criteria</span>
<span className="storageRequest-title">Durability</span>
<div className="storageRequestReview-numbers">
<CardNumbers
title={"Nodes"}
data={data.nodes.toString()}
comment={errors.nodes || "Storage nodes required"}
onChange={onNodesChange}
hasError={!!errors.nodes}></CardNumbers>
onValidation={isInvalidNodes}
helper="Number of storage nodes"></CardNumbers>
<CardNumbers
title={"Tolerance"}
data={data.tolerance.toString()}
comment={errors.tolerance || "Failure nodes tolerated"}
onChange={onToleranceChange}
hasError={!!errors.tolerance}></CardNumbers>
onValidation={isInvalidTolerance}
helper="Failure node tolerated"></CardNumbers>
<CardNumbers
title={"Proof probability"}
data={data.proofProbability.toString()}
comment={
errors.proofProbability || "Proof request frequency in seconds"
}
onChange={onProofProbabilityChange}
hasError={!!errors.proofProbability}></CardNumbers>
helper="Proof request frequency in seconds"></CardNumbers>
</div>
<Range
<div className="storageRequestReview-presets">
<div className="storageRequestReview-presets-title">
<b>Define your durability profile</b>
<p>
Select the appropriate level of data storage reliability ensuring
your information is protected and accessible.
</p>
</div>
<div className="storageRequestReview-presets-blocs">
<div
onClick={() => onDurabilityChange(0)}
className={classnames(
["storageRequestReview-presets-bloc"],
[
"storageRequestReview-presets--selected",
durability <= 0 || durability > 3,
]
)}>
<div className="storageRequestReview-presets-blocIcon">
<img src="/shape-1.png" width={48} />
</div>
<p>Custom</p>
</div>
<div
onClick={() => onDurabilityChange(1)}
className={classnames(
["storageRequestReview-presets-bloc"],
["storageRequestReview-presets--selected", durability === 1]
)}>
<div className="storageRequestReview-presets-blocIcon">
<img src="/shape-2.png" width={48} />
</div>
<p>Low</p>
</div>
<div
onClick={() => onDurabilityChange(2)}
className={classnames(
["storageRequestReview-presets-bloc"],
["storageRequestReview-presets--selected", durability === 2]
)}>
<div className="storageRequestReview-presets-blocIcon">
<img src="/shape-3.png" width={48} />
</div>
<p>Medium</p>
</div>
<div
onClick={() => onDurabilityChange(3)}
className={classnames(
["storageRequestReview-presets-bloc"],
["storageRequestReview-presets--selected", durability === 3]
)}>
<div className="storageRequestReview-presets-blocIcon">
<img src="/shape-4.png" width={48} />
</div>
<p>High</p>
</div>
</div>
</div>
{/* <Range
onChange={onDurabilityRangeChange}
className={classnames(
["storageRequestReview-range"],
@ -311,79 +368,61 @@ export function StorageRequestReview({ onChangeNextState }: Props) {
max={5}
label=""
value={durability}
/>
/> */}
<span className="storageRequest-title">Commitment</span>
<div className="storageRequestReview-numbers">
<CardNumbers
title={"Contract duration"}
data={data.availability.toString()}
comment={"Contract duration in " + data.availabilityUnit}
onChange={onAvailabilityChange}></CardNumbers>
data={availability}
onChange={onAvailabilityChange}
onValidation={isInvalidAvailability}
repositionCaret={false}
helper="Full period of the contract"></CardNumbers>
<CardNumbers
title={"Collateral"}
data={data.collateral.toString()}
onChange={onCollateralChange}
onValidation={isInvalidNumber}
helper="Reward tokens for hosts"></CardNumbers>
<CardNumbers
title={"Reward"}
data={data.reward.toString()}
comment={"Reward tokens"}
onChange={onRewardChange}></CardNumbers>
<CardNumbers
title={"Collateral"}
data={data.reward.toString()}
comment={"Penality tokens"}
onChange={onCollateralChange}></CardNumbers>
onChange={onRewardChange}
onValidation={isInvalidNumber}
helper="Penality tokens"></CardNumbers>
<div className="storageRequest-price"></div>
{/* <Range
className={classnames(
["storageRequestReview-range"],
["storageRequestReview-range--disabled", price === 0]
)}
labels={["Low", "Average", "Attractive"]}
max={100}
label=""
onChange={onPriceRangeChange}
/> */}
</div>
<Range
className={classnames(
["storageRequestReview-range"],
["storageRequestReview-range--disabled", price === 0]
)}
labels={["Low", "Average", "Attractive"]}
max={3}
label=""
onChange={onPriceRangeChange}
/>
<Alert
Icon={<FileWarning />}
title="Warning"
variant="warning"
className="storageRequestReview-alert">
This request with CID
<b> {cid}</b> will expire in
<b> {plurals("minute", data.expiration)} </b>
after the start. <br />
If no suitable hosts are found matching your storage requirements, you
will incur a charge of X tokens.
</Alert>
<hr className="storageRequestReview-hr" />
<p className="text-center">
<b className=" storageRequestReview-title">
Price comparaison with the market
</b>
</p>
<div className="storageRequestReview-legend">
<div className="storageRequestReview-legendItem">
<span className="storageRequestReview-legendItemColor storageRequestReview-legendItemColor-cheap"></span>
<span>Cheap</span>
</div>
<div className="storageRequestReview-legendItem">
<span className="storageRequestReview-legendItemColor storageRequestReview-legendItemColor-average"></span>
<span>Average</span>
</div>
<div className="storageRequestReview-legendItem">
<span className="storageRequestReview-legendItemColor storageRequestReview-legendItemColor-good"></span>
<span>Good</span>
</div>
<div className="storageRequestReview-legendItem">
<span className="storageRequestReview-legendItemColor storageRequestReview-legendItemColor-excellent"></span>
<span>Excellent</span>
</div>
</div>
<div className="storageRequestReview-bar">
<div className="storageRequestReview-barIndicator"></div>
<div className="storageRequestReview-alert">
<CardNumbers
title={"Expiration"}
data={data.expiration.toString()}
onChange={onExpirationChange}
className="storageRequestReview-expiration"
onValidation={isInvalidNumber}
helper="Request expiration in seconds"></CardNumbers>
<Alert
Icon={<FileWarning />}
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.
</Alert>
</div>
</div>
);

View File

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

View File

@ -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 ? () => <span /> : components[step] || components[0];
return (
<>
@ -193,7 +198,7 @@ export function StorageRequestStepper({ className, open, onClose }: Props) {
Body={<Body onChangeNextState={onChangeNextState} />}
step={step}
onChangeStep={onChangeStep}
progress={progress || isPending}
progress={progress}
isNextDisable={progress || isNextDisable}></Stepper>
</div>

View File

@ -68,7 +68,7 @@ const Purchases = () => {
<Cell value={prettyMilliseconds(duration, { verbose: true })} />,
<Cell value={ask.slots.toString()} />,
<Cell value={ask.reward + " CDX"} />,
<Cell value={prettyMilliseconds(pf, { verbose: true })} />,
<Cell value={(pf / 1000).toString()} />,
<CustomStateCellRender state={p.state} message={p.error} />,
];
}) || [];
@ -93,7 +93,7 @@ const Purchases = () => {
onClose={() => setOpen(false)}
/>
{!open && <Table headers={headers} cells={cells} />}
<Table headers={headers} cells={cells} />
{/* {!cells.length && (
<EmptyPlaceholder