mirror of
https://github.com/logos-storage/logos-storage-marketplace-ui.git
synced 2026-01-08 00:13:06 +00:00
Improve the storage request component
This commit is contained in:
parent
b08ffeaa89
commit
d7f072ba66
BIN
public/shape-1.png
Normal file
BIN
public/shape-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
public/shape-2.png
Normal file
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
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
BIN
public/shape-4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@ -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;
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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%;
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user