mirror of
https://github.com/logos-storage/logos-storage-marketplace-ui.git
synced 2026-01-02 13:33:06 +00:00
Allow to create a storage request for days and months
This commit is contained in:
parent
dc8b42c03a
commit
b90b5bd9fc
@ -76,3 +76,49 @@ test('remove the CID when the file is deleted', async ({ page }) => {
|
||||
await page.locator('.button-icon--small').nth(1).click();
|
||||
await expect(page.locator('#cid')).toBeEmpty()
|
||||
})
|
||||
|
||||
test('create a storage request by using decimal values', async ({ page }) => {
|
||||
await page.goto('/dashboard');
|
||||
await page.locator('a').filter({ hasText: 'Purchases' }).click();
|
||||
await page.getByRole('button', { name: 'Storage Request' }).click();
|
||||
|
||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
||||
]);
|
||||
await expect(page.locator('#cid')).not.toBeEmpty()
|
||||
await expect(page.getByText('Success, the CID has been')).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
|
||||
const value = (Math.random() * 10);
|
||||
await page.getByLabel("Full period of the contract").fill(value.toFixed(1))
|
||||
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
await expect(page.getByText('Your request is being processed.')).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Finish' }).click();
|
||||
await expect(page.getByText('No data.')).not.toBeVisible();
|
||||
await expect(page.getByText(value.toFixed(1) + " days").first()).toBeVisible();
|
||||
})
|
||||
|
||||
test('create a storage request by using months', async ({ page }) => {
|
||||
await page.goto('/dashboard');
|
||||
await page.locator('a').filter({ hasText: 'Purchases' }).click();
|
||||
await page.getByRole('button', { name: 'Storage Request' }).click();
|
||||
|
||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
||||
]);
|
||||
await expect(page.locator('#cid')).not.toBeEmpty()
|
||||
await expect(page.getByText('Success, the CID has been')).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
|
||||
await page.getByLabel("Full period of the contract").fill("3")
|
||||
await page.getByRole('combobox').selectOption('months');
|
||||
await expect(page.getByLabel("Full period of the contract")).toHaveValue("1")
|
||||
await page.getByLabel("Full period of the contract").fill("2")
|
||||
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
await expect(page.getByText('Your request is being processed.')).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Finish' }).click();
|
||||
await expect(page.getByText('No data.')).not.toBeVisible();
|
||||
await expect(page.getByText("2 months").first()).toBeVisible();
|
||||
})
|
||||
@ -29,7 +29,7 @@ const CONFIRM_STATE = 2;
|
||||
|
||||
const defaultAvailabilityData: AvailabilityState = {
|
||||
totalSize: 0.5 * GB,
|
||||
duration: Times.unitValue("days"),
|
||||
duration: Times.value("days"),
|
||||
minPrice: 0,
|
||||
maxCollateral: 0,
|
||||
totalSizeUnit: "gb",
|
||||
@ -62,7 +62,7 @@ export function AvailabilityEdit({
|
||||
}
|
||||
|
||||
if (a) {
|
||||
setAvailability(a);
|
||||
setAvailability({ ...defaultAvailabilityData, ...a });
|
||||
}
|
||||
});
|
||||
}, [dispatch]);
|
||||
|
||||
@ -46,7 +46,7 @@ export function AvailabilityForm({
|
||||
|
||||
const onDurationChange = async (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const element = e.currentTarget;
|
||||
const unitValue = Times.unitValue(availability.durationUnit);
|
||||
const unitValue = Times.value(availability.durationUnit);
|
||||
|
||||
onAvailabilityChange({
|
||||
duration: parseInt(element.value) * unitValue,
|
||||
@ -56,7 +56,7 @@ export function AvailabilityForm({
|
||||
const onDurationUnitChange = async (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
const element = e.currentTarget;
|
||||
const unit = element.value as "hours" | "days" | "months";
|
||||
const unitValue = Times.unitValue(unit);
|
||||
const unitValue = Times.value(unit);
|
||||
|
||||
onAvailabilityChange({
|
||||
duration: unitValue,
|
||||
@ -108,7 +108,7 @@ export function AvailabilityForm({
|
||||
availability.totalSizeUnit
|
||||
).toFixed(2);
|
||||
|
||||
const unitValue = Times.unitValue(availability.durationUnit);
|
||||
const unitValue = Times.value(availability.durationUnit);
|
||||
const duration = availability.duration / unitValue;
|
||||
|
||||
return (
|
||||
|
||||
@ -88,5 +88,5 @@ export const AvailabilityUtils = {
|
||||
"#D2493C22",
|
||||
"#D2493C11",
|
||||
"#D2493C00",
|
||||
]
|
||||
],
|
||||
}
|
||||
@ -294,8 +294,8 @@ describe("files", () => {
|
||||
});
|
||||
|
||||
it("formats date", async () => {
|
||||
const utcDate = new Date(Date.UTC(2024, 10, 20, 11, 36));
|
||||
|
||||
assert.equal(FilesUtils.formatDate(1732102577), "20 Nov 2024, 11:36");
|
||||
|
||||
assert.equal(FilesUtils.formatDate(1732102577), "20 Nov 2024, " + utcDate.getHours() + ":" + utcDate.getMinutes());
|
||||
})
|
||||
})
|
||||
7
src/components/StorageRequestSetup/Commitment.css
Normal file
7
src/components/StorageRequestSetup/Commitment.css
Normal file
@ -0,0 +1,7 @@
|
||||
.commitment {
|
||||
--codex-input-group-background-color: transparent;
|
||||
|
||||
span {
|
||||
right: 155px;
|
||||
}
|
||||
}
|
||||
76
src/components/StorageRequestSetup/Commitment.tsx
Normal file
76
src/components/StorageRequestSetup/Commitment.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
import { InputGroup, Tooltip } from "@codex-storage/marketplace-ui-components";
|
||||
import "../CardNumbers/CardNumbers.css";
|
||||
import "./Commitment.css";
|
||||
|
||||
import { ChangeEvent, useState } from "react";
|
||||
import { classnames } from "../../utils/classnames";
|
||||
import InfoIcon from "../../assets/icons/info.svg?react";
|
||||
import { attributes } from "../../utils/attributes";
|
||||
import { Times } from "../../utils/times";
|
||||
|
||||
type Props = {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
onValidation?: (value: string) => string;
|
||||
};
|
||||
|
||||
export function Commitment({ value, onValidation, onChange }: Props) {
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const unitValue = Times.unit(parseFloat(value));
|
||||
const val = parseFloat(value) / Times.value(unitValue);
|
||||
|
||||
const onValueChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
console.info("e.currentTarget.value", e.currentTarget.value);
|
||||
onValueOrUnitChange(
|
||||
(parseFloat(e.currentTarget.value) * Times.value(unitValue)).toFixed(1)
|
||||
);
|
||||
};
|
||||
const onUnitChange = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
onValueOrUnitChange(
|
||||
Times.value(e.currentTarget.value as "days" | "months").toFixed(1)
|
||||
);
|
||||
};
|
||||
|
||||
const onValueOrUnitChange = (val: string) => {
|
||||
onChange(val);
|
||||
|
||||
const msg = onValidation?.(val);
|
||||
|
||||
if (msg) {
|
||||
setError(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
setError("");
|
||||
};
|
||||
|
||||
console.info(val);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(["card-number cardNumber-container commitment"])}
|
||||
{...attributes({ "aria-invalid": !!error })}>
|
||||
<InputGroup
|
||||
id="duration"
|
||||
name="duration"
|
||||
type="number"
|
||||
label="Full period of the contract"
|
||||
isInvalid={!!error}
|
||||
onChange={onValueChange}
|
||||
onGroupChange={onUnitChange}
|
||||
value={Number.isInteger(val) ? val.toString() : val.toFixed(1)}
|
||||
group={[
|
||||
["days", "days"],
|
||||
["months", "months"],
|
||||
]}
|
||||
groupValue={unitValue}
|
||||
/>
|
||||
|
||||
<Tooltip message={error || "The duration of the request in months"}>
|
||||
<InfoIcon></InfoIcon>
|
||||
</Tooltip>
|
||||
<span>{"Contract duration"}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -22,8 +22,7 @@ const CONFIRM_STATE = 2;
|
||||
|
||||
const defaultStorageRequest: StorageRequest = {
|
||||
cid: "",
|
||||
availabilityUnit: "months",
|
||||
availability: 1,
|
||||
availability: Times.value("days"),
|
||||
tolerance: 1,
|
||||
proofProbability: 1,
|
||||
nodes: 3,
|
||||
@ -43,7 +42,7 @@ export function StorageRequestCreate() {
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
WebStorage.get<number>("storage-request-step"),
|
||||
WebStorage.get<StorageRequest>("storage-request"),
|
||||
WebStorage.get<StorageRequest>("storage-request-2"),
|
||||
]).then(([s, data]) => {
|
||||
if (s) {
|
||||
dispatch({
|
||||
@ -83,11 +82,10 @@ export function StorageRequestCreate() {
|
||||
WebStorage.set("storage-request-step", step);
|
||||
|
||||
if (step == CONFIRM_STATE) {
|
||||
const { availabilityUnit, availability, expiration, ...rest } =
|
||||
storageRequest;
|
||||
const { availability, expiration, ...rest } = storageRequest;
|
||||
mutateAsync({
|
||||
...rest,
|
||||
duration: Times.toSeconds(availability, availabilityUnit),
|
||||
duration: availability,
|
||||
expiry: expiration * 60,
|
||||
});
|
||||
} else {
|
||||
@ -101,7 +99,7 @@ export function StorageRequestCreate() {
|
||||
const onStorageRequestChange = (data: Partial<StorageRequest>) => {
|
||||
const val = { ...storageRequest, ...data };
|
||||
|
||||
WebStorage.set("storage-request", val);
|
||||
WebStorage.set("storage-request-2", val);
|
||||
|
||||
setStorageRequest(val);
|
||||
};
|
||||
|
||||
@ -14,6 +14,7 @@ import CommitmentIcon from "../../assets/icons/commitment.svg?react";
|
||||
import RequestDurationIcon from "../../assets/icons/request-duration.svg?react";
|
||||
import { attributes } from "../../utils/attributes";
|
||||
import { Strings } from "../../utils/strings";
|
||||
import { Commitment } from "./Commitment";
|
||||
|
||||
type Durability = {
|
||||
nodes: number;
|
||||
@ -139,14 +140,6 @@ export function StorageRequestReview({
|
||||
return error;
|
||||
}
|
||||
|
||||
// if (!unit.endsWith("s")) {
|
||||
// unit += "s";
|
||||
// }
|
||||
|
||||
// if (!units.includes(unit)) {
|
||||
// return "Invalid unit must one of: minutes, hours, days, months, years";
|
||||
// }
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
@ -165,13 +158,8 @@ export function StorageRequestReview({
|
||||
const onAvailabilityChange = (value: string) => {
|
||||
const [availability] = value.split(" ");
|
||||
|
||||
// if (!availabilityUnit.endsWith("s")) {
|
||||
// availabilityUnit += "s";
|
||||
// }
|
||||
|
||||
onStorageRequestChange({
|
||||
availability: Number(availability),
|
||||
availabilityUnit: "months",
|
||||
});
|
||||
};
|
||||
|
||||
@ -184,18 +172,6 @@ export function StorageRequestReview({
|
||||
const onCollateralChange = (value: string) =>
|
||||
onStorageRequestChange({ collateral: Number(value) });
|
||||
|
||||
// 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 = storageRequest.availability;
|
||||
|
||||
return (
|
||||
@ -286,14 +262,10 @@ export function StorageRequestReview({
|
||||
</div>
|
||||
|
||||
<div className="grid">
|
||||
<CardNumbers
|
||||
helper="The duration of the request in months"
|
||||
id="duration"
|
||||
title={"Full period of the contract"}
|
||||
<Commitment
|
||||
value={availability.toString()}
|
||||
onChange={onAvailabilityChange}
|
||||
onValidation={isInvalidAvailability}
|
||||
unit="Contract duration"></CardNumbers>
|
||||
onValidation={isInvalidAvailability}></Commitment>
|
||||
<CardNumbers
|
||||
helper="Represents how much collateral is asked from hosts that wants to fill a slots"
|
||||
id="collateral"
|
||||
|
||||
@ -16,29 +16,14 @@ export type StoragePriceStepValue = {
|
||||
expiration: number;
|
||||
};
|
||||
|
||||
export type StorageAvailabilityUnit =
|
||||
| "days"
|
||||
| "months"
|
||||
| "years"
|
||||
| "minutes"
|
||||
| "hours";
|
||||
|
||||
export type StorageAvailabilityValue = {
|
||||
value: number;
|
||||
unit: StorageAvailabilityUnit;
|
||||
};
|
||||
|
||||
export type AvailabilityUnit =
|
||||
| "days"
|
||||
| "months"
|
||||
| "years"
|
||||
| "minutes"
|
||||
| "hours";
|
||||
|
||||
export type StorageRequest = {
|
||||
cid: string;
|
||||
availability: number;
|
||||
availabilityUnit: AvailabilityUnit;
|
||||
tolerance: number;
|
||||
proofProbability: number;
|
||||
nodes: number;
|
||||
|
||||
@ -29,7 +29,7 @@ export function useStorageRequestMutation(
|
||||
// }
|
||||
|
||||
WebStorage.delete("storage-request-step");
|
||||
WebStorage.delete("storage-request");
|
||||
WebStorage.delete("storage-request-2");
|
||||
|
||||
setError(null);
|
||||
|
||||
|
||||
12
src/utils/bytes.test.ts
Normal file
12
src/utils/bytes.test.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { assert, describe, it } from "vitest";
|
||||
import { Bytes } from "./bytes";
|
||||
import { GB } from "./constants";
|
||||
|
||||
describe("bytes", () => {
|
||||
it("display the bytes", async () => {
|
||||
assert.equal(Bytes.pretty(0), "0 B");
|
||||
assert.equal(Bytes.pretty(512), "512.0 B");
|
||||
assert.equal(Bytes.pretty(1025), "1.0 KB");
|
||||
assert.equal(Bytes.pretty(GB), "1.0 GB");
|
||||
});
|
||||
})
|
||||
@ -1,14 +1,14 @@
|
||||
export const Bytes = {
|
||||
pretty(bytes: number) {
|
||||
const sizes = ["bytes", "KB", "MB", "GB", "TB"];
|
||||
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
||||
if (bytes == 0) {
|
||||
return "0 b";
|
||||
return "0 B";
|
||||
}
|
||||
|
||||
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)).toString());
|
||||
|
||||
if (i == 0) {
|
||||
return bytes + " " + sizes[i];
|
||||
return bytes.toFixed(1) + " " + sizes[i];
|
||||
}
|
||||
|
||||
return (bytes / Math.pow(1024, i)).toFixed(1) + " " + sizes[i];
|
||||
|
||||
28
src/utils/times.test.ts
Normal file
28
src/utils/times.test.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { assert, describe, it } from "vitest";
|
||||
import { Times } from "./times";
|
||||
|
||||
describe("times", () => {
|
||||
it("display the times", async () => {
|
||||
assert.equal(Times.pretty(0), "0 second");
|
||||
assert.equal(Times.pretty(2), "2 seconds");
|
||||
assert.equal(Times.pretty(60), "1 minute");
|
||||
assert.equal(Times.pretty(90), "1.5 minutes");
|
||||
assert.equal(Times.pretty(3600), "1 hour");
|
||||
assert.equal(Times.pretty(3600 * 2), "2 hours");
|
||||
assert.equal(Times.pretty(3600 * 24), "1 day");
|
||||
assert.equal(Times.pretty(3600 * 36), "1.5 days");
|
||||
assert.equal(Times.pretty(3600 * 24 * 30), "1 month");
|
||||
});
|
||||
|
||||
it("guess the time unit", async () => {
|
||||
assert.equal(Times.unit(0), "hours");
|
||||
assert.equal(Times.unit(3600 * 24), "days");
|
||||
assert.equal(Times.unit(3600 * 24 * 30), "months");
|
||||
})
|
||||
|
||||
it("get the seconds for a time unit given", async () => {
|
||||
assert.equal(Times.value("hours"), 3600);
|
||||
assert.equal(Times.value("days"), 3600 * 24);
|
||||
assert.equal(Times.value("months"), 3600 * 24 * 30);
|
||||
})
|
||||
})
|
||||
@ -6,31 +6,33 @@ export type TimesUnit =
|
||||
| "hours"
|
||||
| "seconds";
|
||||
|
||||
const plural = (value: number, unit: TimesUnit) =>
|
||||
value > 1 ? value + ` ${unit}` : value + ` ${unit.slice(0, -1)}`;
|
||||
const plural = (value: number, unit: TimesUnit) => {
|
||||
const val = Number.isInteger(value) ? value : value.toFixed(1)
|
||||
return value > 1 ? val + ` ${unit}` : val + ` ${unit.slice(0, -1)}`;
|
||||
}
|
||||
|
||||
export const Times = {
|
||||
toSeconds(value: number, unit: TimesUnit) {
|
||||
let seconds = value;
|
||||
let val = value;
|
||||
/* eslint-disable no-fallthrough */
|
||||
switch (unit) {
|
||||
// @ts-expect-error - We don't want to break
|
||||
case "years":
|
||||
seconds *= 365;
|
||||
val *= 365;
|
||||
// @ts-expect-error - We don't want to break
|
||||
case "months":
|
||||
seconds *= 30;
|
||||
val *= 30;
|
||||
// @ts-expect-error - We don't want to break
|
||||
case "days":
|
||||
seconds *= 24;
|
||||
val *= 24;
|
||||
// @ts-expect-error - We don't want to break
|
||||
case "hours":
|
||||
seconds *= 60;
|
||||
val *= 60;
|
||||
case "minutes":
|
||||
seconds *= 60;
|
||||
val *= 60;
|
||||
}
|
||||
|
||||
return seconds;
|
||||
return val;
|
||||
},
|
||||
|
||||
pretty(value: number) {
|
||||
@ -62,7 +64,7 @@ export const Times = {
|
||||
return plural(value, "seconds");
|
||||
},
|
||||
|
||||
unit(value: number) {
|
||||
unit(value: number): "months" | "days" | "hours" {
|
||||
let seconds = 30 * 24 * 60 * 60;
|
||||
|
||||
if (value >= seconds) {
|
||||
@ -77,7 +79,7 @@ export const Times = {
|
||||
return "hours"
|
||||
},
|
||||
|
||||
unitValue(unit: "hours" | "days" | "months") {
|
||||
value(unit: "hours" | "days" | "months") {
|
||||
switch (unit) {
|
||||
case "months": {
|
||||
return 30 * 24 * 60 * 60
|
||||
@ -90,4 +92,5 @@ export const Times = {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user