Skip to content

Commit

Permalink
feat: position route (#47)
Browse files Browse the repository at this point in the history
* feat: position route

* fix: issue

* chore: position page

* lint
  • Loading branch information
Picodes authored Dec 19, 2024
1 parent a3f3a46 commit de6c327
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 7 deletions.
20 changes: 20 additions & 0 deletions src/api/services/liquidity.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { api } from "../index.server";
import { fetchWithLogs } from "../utils";

export abstract class LiquidityService {
static async #fetch<R, T extends { data: R; status: number; response: Response }>(
call: () => Promise<T>,
resource = "Positions",
): Promise<NonNullable<T["data"]>> {
const { data, status } = await fetchWithLogs(call);

if (status === 404) throw new Response(`${resource} not found`, { status });
if (status === 500) throw new Response(`${resource} unavailable`, { status });
if (data == null) throw new Response(`${resource} unavailable`, { status });
return data;
}

static async getForUser(query: Parameters<typeof api.v4.liquidity.index.get>["0"]["query"]) {
return await LiquidityService.#fetch(async () => api.v4.liquidity.index.get({ query }));
}
}
2 changes: 1 addition & 1 deletion src/api/services/opportunity/opportunity.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export abstract class OpportunityService {

//TODO: updates tags to take an array
if (config.tags && !opportunityWithCampaigns.tags.includes(config.tags?.[0]))
throw new Response("Opportunity inacessible", { status: 403 });
throw new Response("Opportunity inaccessible", { status: 403 });

return opportunityWithCampaigns;
}
Expand Down
32 changes: 32 additions & 0 deletions src/components/element/position/PositionLibrary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { PositionT } from "@merkl/api/dist/src/modules/v4/liquidity";
import { Text, Title } from "dappkit";
import { useMemo } from "react";
import OpportunityPagination from "../opportunity/OpportunityPagination";
import { PositionTable } from "./PositionTable";
import PositionTableRow from "./PositionTableRow";

export type IProps = {
positions: PositionT[];
count?: number;
};

export default function PositionLibrary(props: IProps) {
const { positions, count } = props;

const rows = useMemo(() => {
return positions?.map(row => <PositionTableRow key={crypto.randomUUID()} row={row} />);
}, [positions]);

return (
<PositionTable
dividerClassName={index => (index < 2 ? "bg-accent-8" : "bg-main-8")}
header={
<Title h={5} className="!text-main-11 w-full">
Your Liquidity
</Title>
}
footer={count !== undefined && <OpportunityPagination count={count} />}>
{!!rows.length ? rows : <Text>No positions detected</Text>}
</PositionTable>
);
}
23 changes: 23 additions & 0 deletions src/components/element/position/PositionTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createTable } from "dappkit";

export const [PositionTable, PositionRow, PositionColumns] = createTable({
source: {
name: "Source",
size: "minmax(120px,150px)",
compact: "1fr",
className: "justify-start",
main: true,
},
flags: {
name: "Flags",
size: "minmax(170px,1fr)",
compactSize: "1fr",
className: "justify-start",
},
tokens: {
name: "Tokens",
size: "minmax(30px,1fr)",
compactSize: "minmax(20px,1fr)",
className: "justify-start",
},
});
43 changes: 43 additions & 0 deletions src/components/element/position/PositionTableRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { PositionT } from "@merkl/api/dist/src/modules/v4/liquidity";
import { type Component, Group, PrimitiveTag, Text, mergeClass } from "dappkit";
import React from "react";
import Token from "../token/Token";
import { PositionRow } from "./PositionTable";

export type PositionRowProps = Component<{
row: PositionT;
}>;

export default function PositionTableRow({ row, className, ...props }: PositionRowProps) {
return (
<PositionRow
{...props}
className={mergeClass("cursor-pointer", className)}
sourceColumn={<Group className="flex-nowrap">{row.opportunity.name}</Group>}
flagsColumn={
<Group className="flex-nowrap">
<PrimitiveTag size="xs" className="pointer-events-none" look="soft">
<Text>{row.flags?.id}</Text>
</PrimitiveTag>
<PrimitiveTag size="xs" className="pointer-events-none" look="soft">
<Text>{row.flags?.range}</Text>
</PrimitiveTag>
</Group>
}
tokensColumn={
<Group className="flex-nowrap">
{row.tokens.map((token, index) => (
<>
<Token token={token.token} key={index.toString()} />
{token.breakdown.map((breakdown, index) => (
<React.Fragment key={index.toString()}>
{breakdown.type} {breakdown.value}
</React.Fragment>
))}
</>
))}
</Group>
}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export async function loader({ params: { id, type, chain: chainId }, request }:

const { rewards, count, total } = await RewardService.getManyFromRequest(request, {
chainId: chain.id,
campaignId: campaigns?.[0]?.campaignId,
});

return json({
Expand Down
26 changes: 26 additions & 0 deletions src/routes/_merkl.users.$address.$chainId.liquidity.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { LoaderFunctionArgs } from "@remix-run/node";
import { json, useLoaderData } from "@remix-run/react";
import { Container } from "dappkit";
import { LiquidityService } from "src/api/services/liquidity.service";
import PositionLibrary from "src/components/element/position/PositionLibrary";
import { isAddress } from "viem";

export async function loader({ params: { address, chainId } }: LoaderFunctionArgs) {
if (!address || !isAddress(address)) throw "";
if (!chainId && Number(chainId).toString() === chainId) throw "";

const positions = await LiquidityService.getForUser({
address,
chainId: Number(chainId),
});
return json({ positions });
}

export default function Index() {
const { positions } = useLoaderData<typeof loader>();
return (
<Container>
<PositionLibrary positions={positions} />
</Container>
);
}
5 changes: 0 additions & 5 deletions src/routes/_merkl.users.$address.liquidity.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion src/routes/_merkl.users.$address.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export default function Index() {
Liquidity
</>
),
link: `/users/${address}/liquidity`,
link: `/users/${address}/${chainId}/liquidity`,
key: crypto.randomUUID(),
},
{
Expand Down

0 comments on commit de6c327

Please sign in to comment.