Skip to content

Commit

Permalink
feat: add slot page
Browse files Browse the repository at this point in the history
  • Loading branch information
VmMad committed Feb 20, 2024
1 parent 59655f3 commit 90a36e6
Show file tree
Hide file tree
Showing 12 changed files with 321 additions and 0 deletions.
11 changes: 11 additions & 0 deletions api/src/models/api/nova/ISlotRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface ISlotRequest {
/**
* The network to search on.
*/
network: string;

/**
* The slot index to get the details for.
*/
slotIndex: string;
}
8 changes: 8 additions & 0 deletions api/src/models/api/nova/ISlotResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { SlotCommitment } from "@iota/sdk-nova";

Check failure on line 1 in api/src/models/api/nova/ISlotResponse.ts

View workflow job for this annotation

GitHub Actions / lint-check (api)

Unable to resolve path to module '@iota/sdk-nova'

export interface ISlotResponse {
/**
* The deserialized slot.
*/
slot?: SlotCommitment;
}
1 change: 1 addition & 0 deletions api/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,4 +244,5 @@ export const routes: IRoute[] = [
},
{ path: "/nova/block/:network/:blockId", method: "get", folder: "nova/block", func: "get" },
{ path: "/nova/block/metadata/:network/:blockId", method: "get", folder: "nova/block/metadata", func: "get" },
{ path: "/nova/slot/:network/:slotIndex", method: "get", folder: "nova/slot", func: "get" },
];
30 changes: 30 additions & 0 deletions api/src/routes/nova/slot/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ServiceFactory } from "../../../factories/serviceFactory";
import { ISlotRequest } from "../../../models/api/nova/ISlotRequest";
import { ISlotResponse } from "../../../models/api/nova/ISlotResponse";
import { IConfiguration } from "../../../models/configuration/IConfiguration";
import { NOVA } from "../../../models/db/protocolVersion";
import { NetworkService } from "../../../services/networkService";
import { NovaApiService } from "../../../services/nova/novaApiService";
import { ValidationHelper } from "../../../utils/validationHelper";

/**
* Fetch the block from the network.
* @param _ The configuration.
* @param request The request.
* @returns The response.
*/
export async function get(_: IConfiguration, request: ISlotRequest): Promise<ISlotResponse> {
const networkService = ServiceFactory.get<NetworkService>("network");
const networks = networkService.networkNames();
ValidationHelper.oneOf(request.network, networks, "network");
ValidationHelper.numberFromString(request.slotIndex, "slotIndex");

const networkConfig = networkService.get(request.network);

if (networkConfig.protocolVersion !== NOVA) {
return {};
}

const novaApiService = ServiceFactory.get<NovaApiService>(`api-service-${networkConfig.network}`);
return novaApiService.getSlotCommitment(Number(request.slotIndex));
}
11 changes: 11 additions & 0 deletions api/src/services/nova/novaApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { INftDetailsResponse } from "../../models/api/nova/INftDetailsResponse";
import { IOutputDetailsResponse } from "../../models/api/nova/IOutputDetailsResponse";
import { IRewardsResponse } from "../../models/api/nova/IRewardsResponse";
import { ISearchResponse } from "../../models/api/nova/ISearchResponse";
import { ISlotResponse } from "../../models/api/nova/ISlotResponse";
import { INetwork } from "../../models/db/INetwork";
import { HexHelper } from "../../utils/hexHelper";
import { SearchExecutor } from "../../utils/nova/searchExecutor";
Expand Down Expand Up @@ -261,6 +262,16 @@ export class NovaApiService {
return manaRewardsResponse ? { outputId, manaRewards: manaRewardsResponse } : { outputId, message: "Rewards data not found" };
}

public async getSlotCommitment(slotIndex: number): Promise<ISlotResponse> {
try {
const slot = await this.client.getCommitmentByIndex(slotIndex);

return { slot };
} catch (e) {
logger.error(`Failed fetching slot with slot index ${slotIndex}. Cause: ${e}`);
}
}

/**
* Find item on the stardust network.
* @param query The query to use for finding items.
Expand Down
2 changes: 2 additions & 0 deletions client/src/app/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import StardustOutputPage from "./routes/stardust/OutputPage";
import NovaBlockPage from "./routes/nova/Block";
import NovaOutputPage from "./routes/nova/OutputPage";
import NovaSearch from "./routes/nova/Search";
import NovaSlotPage from "./routes/nova/SlotPage";
import StardustSearch from "./routes/stardust/Search";
import StardustStatisticsPage from "./routes/stardust/statistics/StatisticsPage";
import StardustTransactionPage from "./routes/stardust/TransactionPage";
Expand Down Expand Up @@ -178,6 +179,7 @@ const buildAppRoutes = (protocolVersion: string, withNetworkContext: (wrappedCom
<Route path="/:network/block/:blockId" key={keys.next().value} component={NovaBlockPage} />,
<Route path="/:network/output/:outputId" key={keys.next().value} component={NovaOutputPage} />,
<Route path="/:network/search/:query?" key={keys.next().value} component={NovaSearch} />,
<Route path="/:network/slot/:slotIndex" key={keys.next().value} component={NovaSlotPage} />,
];

return (
Expand Down
86 changes: 86 additions & 0 deletions client/src/app/routes/nova/SlotPage.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
@import "../../../scss/fonts";
@import "../../../scss/mixins";
@import "../../../scss/media-queries";
@import "../../../scss/variables";

.slot-page {
display: flex;
flex-direction: column;

.wrapper {
display: flex;
justify-content: center;

.inner {
display: flex;
flex: 1;
flex-direction: column;
max-width: $desktop-width;
margin: 40px 25px;

@include desktop-down {
flex: unset;
width: 100%;
max-width: 100%;
margin: 40px 24px;
padding-right: 24px;
padding-left: 24px;

> .row {
flex-direction: column;
}
}

@include tablet-down {
margin: 28px 0;
}

.slot-page--header {
display: flex;
align-items: center;
justify-content: space-between;
}

.section {
padding-top: 44px;

.section--header {
margin-top: 44px;
}

.card--content__output {
margin-top: 20px;
}
}
}
}

.tooltip {
.children {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.tooltip__special {
background-color: #fff4df;
border-radius: 4px;
padding: 0 4px;
font-weight: 400;
}

.wrap {
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
width: 170px;

.arrow {
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
}
}
}
}
90 changes: 90 additions & 0 deletions client/src/app/routes/nova/SlotPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React from "react";
import Modal from "~/app/components/Modal";
import { ModalData } from "~/app/components/ModalProps";
import { RouteComponentProps } from "react-router-dom";
import TruncatedId from "~/app/components/stardust/TruncatedId";
import classNames from "classnames";
import useSlotData from "~/helpers/nova/hooks/useSlot";
import "./SlotPage.scss";

interface SlotPageProps {
network: string;
slotIndex: string;
}

export default function SlotPage({
match: {
params: { network, slotIndex },
},
}: RouteComponentProps<SlotPageProps>): React.JSX.Element {
const { slotCommitment } = useSlotData(network, slotIndex);

const message: ModalData = {
title: "Slot Page",
description: "<p>Slot Information here</p>",
};

const dataRows: IDataRow[] = [
{
label: "Slot Index",
value: slotCommitment?.slot,
highlighted: true,
},
{
label: "RMC",
value: slotCommitment?.referenceManaCost.toString(),
},
];

return (
<section className="slot-page">
<div className="wrapper">
<div className="inner">
<div className="slot-page--header">
<div className="row middle">
<h1>Slot</h1>
<Modal icon="info" data={message} />
</div>
</div>
<div className="section">
<div className="section--header row row--tablet-responsive middle space-between">
<div className="row middle">
<h2>General</h2>
</div>
</div>
{dataRows.map((dataRow, index) => {
if (dataRow.value || dataRow.truncatedId) {
return <DataRow key={index} {...dataRow} />;
}
})}
</div>
</div>
</div>
</section>
);
}

interface IDataRow {
label: string;
value?: string | number;
highlighted?: boolean;
truncatedId?: {
id: string;
link?: string;
showCopyButton?: boolean;
};
}
const DataRow = ({ label, value, truncatedId, highlighted }: IDataRow) => {
return (
<div className="section--data">
<div className="label">{label}</div>
<div className={classNames("value code", { highlighted })}>
{truncatedId ? (
<TruncatedId id={truncatedId.id} link={truncatedId.link} showCopyButton={truncatedId.showCopyButton} />
) : (
value
)}
</div>
</div>
);
};
54 changes: 54 additions & 0 deletions client/src/helpers/nova/hooks/useSlot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { SlotCommitment } from "@iota/sdk-wasm-nova/web";
import { plainToInstance } from "class-transformer";
import { useEffect, useState } from "react";
import { ServiceFactory } from "~/factories/serviceFactory";
import { useIsMounted } from "~/helpers/hooks/useIsMounted";
import { NOVA } from "~/models/config/protocolVersion";
import { NovaApiClient } from "~/services/nova/novaApiClient";

interface IUseSlotData {
slotCommitment: SlotCommitment | null;
error: string | undefined;
isLoading: boolean;
}

export default function useSlotData(network: string, slotIndex: string): IUseSlotData {
const isMounted = useIsMounted();
const [apiClient] = useState(ServiceFactory.get<NovaApiClient>(`api-client-${NOVA}`));
const [slotCommitment, setSlotCommitment] = useState<SlotCommitment | null>(null);
const [error, setError] = useState<string | undefined>();
const [isLoading, setIsLoading] = useState<boolean>(true);

useEffect(() => {
setIsLoading(true);
setSlotCommitment(null);
if (!slotCommitment) {
// eslint-disable-next-line no-void
void (async () => {
apiClient
.getSlotCommitment({
network,
slotIndex,
})
.then((response) => {
if (isMounted) {
const slot = plainToInstance(SlotCommitment, response.slot) as unknown as SlotCommitment;
setSlotCommitment(slot);
setError(response.error);
}
})
.finally(() => {
setIsLoading(false);
});
})();
} else {
setIsLoading(false);
}
}, [network, slotIndex]);

return {
slotCommitment,
error,
isLoading,
};
}
11 changes: 11 additions & 0 deletions client/src/models/api/nova/ISlotRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface ISlotRequest {
/**
* The network to search on.
*/
network: string;

/**
* The slot index to get the commitment for.
*/
slotIndex: string;
}
6 changes: 6 additions & 0 deletions client/src/models/api/nova/ISlotResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { SlotCommitment } from "@iota/sdk-wasm-nova/web";
import { IResponse } from "../IResponse";

export interface ISlotResponse extends IResponse {
slot: SlotCommitment;
}
11 changes: 11 additions & 0 deletions client/src/services/nova/novaApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import { IAddressDetailsRequest } from "~/models/api/nova/address/IAddressDetail
import { IAddressDetailsResponse } from "~/models/api/nova/address/IAddressDetailsResponse";
import { IFoundriesResponse } from "~/models/api/nova/foundry/IFoundriesResponse";
import { IFoundriesRequest } from "~/models/api/nova/foundry/IFoundriesRequest";
import { ISlotRequest } from "~/models/api/nova/ISlotRequest";
import { ISlotResponse } from "~/models/api/nova/ISlotResponse";

/**
* Class to handle api communications on nova.
Expand Down Expand Up @@ -155,6 +157,15 @@ export class NovaApiClient extends ApiClient {
return this.callApi<unknown, IRewardsResponse>(`nova/output/rewards/${request.network}/${request.outputId}`, "get");
}

/**
* Get the slot commitment.
* @param request The request to send.
* @returns The response from the request.
*/
public async getSlotCommitment(request: ISlotRequest): Promise<ISlotResponse> {
return this.callApi<unknown, ISlotResponse>(`nova/slot/${request.network}/${request.slotIndex}`, "get");
}

/**
* Get the stats.
* @param request The request to send.
Expand Down

0 comments on commit 90a36e6

Please sign in to comment.