diff --git a/merkl.config.ts b/merkl.config.ts index 85a3eb38..2b4cb969 100644 --- a/merkl.config.ts +++ b/merkl.config.ts @@ -1,6 +1,6 @@ import { createColoring } from "dappkit"; import { createConfig } from "src/config/type"; -import { http } from "viem"; +import { createClient, custom, http } from "viem"; import { mainnet, optimism, @@ -37,6 +37,7 @@ import { } from "viem/chains"; import { coinbaseWallet, walletConnect } from "wagmi/connectors"; import hero from "src/customer/assets/images/hero.jpg?url"; +import { eip712WalletActions } from "viem/zksync"; export default createConfig({ appName: "Merkl", @@ -44,26 +45,11 @@ export default createConfig({ defaultTheme: "merkl", themes: { merkl: { - base: createColoring( - ["#1F2333", "#B8AAFD", "#131620"], - ["#FCF8F5", "#B8AAFD", "white"] - ), - info: createColoring( - ["#2ABDFF", "#2ABDFF", "#131620"], - ["#FFFFFF", "#40B66B", "white"] - ), - good: createColoring( - ["#40B66B", "#40B66B", "#131620"], - ["#FFFFFF", "#40B66B", "white"] - ), - warn: createColoring( - ["#ff9600", "#ff9600", "#131620"], - ["#FFFFFF", "#40B66B", "white"] - ), - harm: createColoring( - ["#d22e14", "#d22e14", "#131620"], - ["#FFFFFF", "#40B66B", "white"] - ), + base: createColoring(["#1F2333", "#B8AAFD", "#131620"], ["#FCF8F5", "#B8AAFD", "white"]), + info: createColoring(["#2ABDFF", "#2ABDFF", "#131620"], ["#FFFFFF", "#40B66B", "white"]), + good: createColoring(["#40B66B", "#40B66B", "#131620"], ["#FFFFFF", "#40B66B", "white"]), + warn: createColoring(["#ff9600", "#ff9600", "#131620"], ["#FFFFFF", "#40B66B", "white"]), + harm: createColoring(["#d22e14", "#d22e14", "#131620"], ["#FFFFFF", "#40B66B", "white"]), }, }, sizing: { @@ -80,11 +66,6 @@ export default createConfig({ route: "/", key: crypto.randomUUID(), }, - dashboard: { - icon: "RiDashboard2Fill", - route: "/user", - key: crypto.randomUUID(), - }, opportunities: { icon: "RiPlanetFill", route: "/opportunities", @@ -95,12 +76,6 @@ export default createConfig({ route: "/protocols", key: crypto.randomUUID(), }, - bridge: { - icon: "RiVipCrown2Fill", - route: "/bridge", - key: crypto.randomUUID(), - }, - faq: { icon: "RiQuestionFill", route: "/faq", key: crypto.randomUUID() }, terms: { icon: "RiCompassesLine", route: "/terms", @@ -156,6 +131,12 @@ export default createConfig({ taiko, scroll, ], + client({ chain }) { + if (chain.id === zksync.id) + return createClient({ chain, transport: custom(window.ethereum!) }).extend(eip712WalletActions()); + return createClient({ chain, transport: http() }); + }, + ssr: true, connectors: [ coinbaseWallet(), walletConnect({ @@ -169,9 +150,5 @@ export default createConfig({ }, }), ], - transports: { - [zksync.id]: http(), - [optimism.id]: http(), - }, }, }); diff --git a/package.json b/package.json index 97e1a2f7..2900a239 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ ], "dependencies": { "@acab/ecsstatic": "^0.8.0", - "@angleprotocol/merkl-api": "0.10.69", + "@merkl/api": "0.10.78", "@ariakit/react": "^0.4.12", "@elysiajs/eden": "^1.1.3", "@emotion/css": "^11.13.4", diff --git a/packages/dappkit b/packages/dappkit index 8ced032d..71bc4b81 160000 --- a/packages/dappkit +++ b/packages/dappkit @@ -1 +1 @@ -Subproject commit 8ced032d34bbd5186fabb31ecf741bff89d0bab4 +Subproject commit 71bc4b81aa0aa5eb4f1e857721cbd22633c78f86 diff --git a/src/api/index.client.ts b/src/api/index.client.ts index 5a83e1da..8e38c1f2 100644 --- a/src/api/index.client.ts +++ b/src/api/index.client.ts @@ -1,4 +1,4 @@ -import { MerklApi } from "@angleprotocol/merkl-api"; +import { MerklApi } from "@merkl/api"; const api = MerklApi((window as { ENV?: { API_URL?: string } })?.ENV?.API_URL ?? "https://api.merkl.xyz"); diff --git a/src/api/index.server.ts b/src/api/index.server.ts index ec21ff7b..b7445045 100644 --- a/src/api/index.server.ts +++ b/src/api/index.server.ts @@ -1,4 +1,4 @@ -import { MerklApi } from "@angleprotocol/merkl-api"; +import { MerklApi } from "@merkl/api"; const api = MerklApi(process.env.API_URL ?? "https://api.merkl.xyz"); diff --git a/src/api/services/chain.service.ts b/src/api/services/chain.service.ts index 0980468b..b5b6f5f8 100644 --- a/src/api/services/chain.service.ts +++ b/src/api/services/chain.service.ts @@ -1,4 +1,4 @@ -import type { Chain } from "@angleprotocol/merkl-api"; +import type { Chain } from "@merkl/api"; import { api } from "../index.server"; export abstract class ChainService { diff --git a/src/api/services/opportunity.service.ts b/src/api/services/opportunity.service.ts index 2b45abbf..ae4ca252 100644 --- a/src/api/services/opportunity.service.ts +++ b/src/api/services/opportunity.service.ts @@ -1,4 +1,4 @@ -import type { Opportunity } from "@angleprotocol/merkl-api"; +import type { Opportunity } from "@merkl/api"; import config from "merkl.config"; import { api } from "../index.server"; @@ -48,8 +48,11 @@ export abstract class OpportunityService { return query; } - static async getManyFromRequest(request: Request) { - return OpportunityService.getMany(OpportunityService.#getQueryFromRequest(request)); + static async getManyFromRequest( + request: Request, + overrides?: Parameters[0]["query"], + ) { + return OpportunityService.getMany(Object.assign(OpportunityService.#getQueryFromRequest(request), overrides ?? {})); } static async getMany( diff --git a/src/api/services/protocol.service.ts b/src/api/services/protocol.service.ts index a0664eef..e3446108 100644 --- a/src/api/services/protocol.service.ts +++ b/src/api/services/protocol.service.ts @@ -1,4 +1,4 @@ -import type { Protocol } from "@angleprotocol/merkl-api"; +import type { Protocol } from "@merkl/api"; import { api } from "../index.server"; export abstract class ProtocolService { @@ -14,9 +14,47 @@ export abstract class ProtocolService { return data; } + /** + * Retrieves protocols query params from page request + * @param request request containing query params such as paginatio + * @param override params for which to override value + * @returns query + */ + static #getQueryFromRequest( + request: Request, + override?: Parameters[0]["query"], + ) { + const page = new URL(request.url).searchParams.get("page"); + const items = new URL(request.url).searchParams.get("items"); + const search = new URL(request.url).searchParams.get("search"); + + const [sort, order] = new URL(request.url).searchParams.get("sort")?.split("-") ?? []; + + const filters = Object.assign( + { items, sort, order, name: search, page }, + override ?? {}, + page !== null && { page: Number(page) - 1 }, + ); + + const query = Object.entries(filters).reduce( + (_query, [key, filter]) => Object.assign(_query, filter == null ? {} : { [key]: filter }), + {}, + ); + + return query; + } + static async get(query: { id: string }): Promise { const protocol = await ProtocolService.#fetch(async () => api.v4.protocols(query).get({ query })); return protocol; } + + static async getManyFromRequest(request: Request): Promise<{ protocols: Protocol[]; count: number }> { + const query = ProtocolService.#getQueryFromRequest(request); + const protocols = await ProtocolService.#fetch(async () => api.v4.protocols.index.get({ query })); + const count = await ProtocolService.#fetch(async () => api.v4.protocols.count.get({ query })); + + return { protocols, count }; + } } diff --git a/src/api/services/reward.service.ts b/src/api/services/reward.service.ts index b081cef0..a3bdb278 100644 --- a/src/api/services/reward.service.ts +++ b/src/api/services/reward.service.ts @@ -1,4 +1,4 @@ -import type { Reward } from "@angleprotocol/merkl-api"; +import type { Reward } from "@merkl/api"; import { api } from "../index.server"; export abstract class RewardService { diff --git a/src/api/services/token.service.ts b/src/api/services/token.service.ts index bbefb1fa..2f6767d7 100644 --- a/src/api/services/token.service.ts +++ b/src/api/services/token.service.ts @@ -1,10 +1,10 @@ -import type { Token } from "@angleprotocol/merkl-api"; +import type { Token } from "@merkl/api"; import { api } from "../index.server"; export abstract class TokenService { static async #fetch( call: () => Promise, - resource = "Chain", + resource = "Token", ): Promise> { const { data, status } = await call(); @@ -14,6 +14,44 @@ export abstract class TokenService { return data; } + /** + * Retrieves tokens query params from page request + * @param request request containing query params such as pagination + * @param override params for which to override value + * @returns query + */ + static #getQueryFromRequest( + request: Request, + override?: Parameters[0]["query"], + ) { + const page = new URL(request.url).searchParams.get("page"); + const items = new URL(request.url).searchParams.get("items"); + const search = new URL(request.url).searchParams.get("search"); + + const [sort, order] = new URL(request.url).searchParams.get("sort")?.split("-") ?? []; + + const filters = Object.assign( + { items, sort, order, name: search, page }, + override ?? {}, + page !== null && { page: Number(page) - 1 }, + ); + + const query = Object.entries(filters).reduce( + (_query, [key, filter]) => Object.assign(_query, filter == null ? {} : { [key]: filter }), + {}, + ); + + return query; + } + + static async getManyFromRequest(request: Request): Promise<{ tokens: Token[]; count: number }> { + const query = TokenService.#getQueryFromRequest(request); + const tokens = await TokenService.#fetch(async () => api.v4.tokens.index.get({ query })); + const count = await TokenService.#fetch(async () => api.v4.tokens.count.get({ query })); + + return { tokens, count }; + } + static async getMany(query: Parameters[0]["query"]): Promise { const tokens = await TokenService.#fetch(async () => api.v4.tokens.index.get({ query })); diff --git a/src/api/types.ts b/src/api/types.ts index d8a389a0..6b369f0a 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -1,3 +1,3 @@ -import type { MerklApi } from "@angleprotocol/merkl-api"; +import type { MerklApi } from "@merkl/api"; export type Api = ReturnType; diff --git a/src/components/composite/Hero.tsx b/src/components/composite/Hero.tsx index f72dede4..d3d89753 100644 --- a/src/components/composite/Hero.tsx +++ b/src/components/composite/Hero.tsx @@ -1,15 +1,14 @@ -import type { Campaign, Opportunity } from "@angleprotocol/merkl-api"; +import type { Opportunity } from "@merkl/api"; import { useLocation } from "@remix-run/react"; -import { Box, Container, Divider, Group, Icon, type IconProps, Icons, Text, Title, Value } from "dappkit"; +import { Box, Container, Divider, Group, Icon, type IconProps, Icons, Text, Title } from "dappkit"; import { Button } from "dappkit"; import config from "merkl.config"; -import moment from "moment"; -import { type PropsWithChildren, type ReactNode, useMemo } from "react"; -import { formatUnits, parseUnits } from "viem"; +import type { PropsWithChildren, ReactNode } from "react"; export type HeroProps = PropsWithChildren<{ icons?: IconProps[]; title: ReactNode; + breadcrumbs?: { name: string; link: string; component?: ReactNode }[]; navigation?: { label: ReactNode; link: string }; description: ReactNode; tags?: ReactNode[]; @@ -17,66 +16,31 @@ export type HeroProps = PropsWithChildren<{ opportunity?: Opportunity; }>; -export default function Hero({ navigation, icons, title, description, tags, children, opportunity, tabs }: HeroProps) { +export default function Hero({ navigation, breadcrumbs, icons, title, description, tags, tabs, children }: HeroProps) { const location = useLocation(); - const filteredCampaigns = useMemo(() => { - if (!opportunity?.campaigns) return null; - const now = moment().unix(); - return opportunity.campaigns?.filter((c: Campaign) => Number(c.endTimestamp) > now); - }, [opportunity]); - - const totalRewards = useMemo(() => { - const amounts = filteredCampaigns?.map(campaign => { - const duration = campaign.endTimestamp - campaign.startTimestamp; - const dayspan = BigInt(duration) / BigInt(3600 * 24); - - return parseUnits(campaign.amount, 0) / BigInt(dayspan); - }); - - const sum = amounts?.reduce((accumulator, currentValue) => accumulator + currentValue, 0n); - if (!sum) return "0.0"; - return formatUnits(sum, 18); - }, [filteredCampaigns]); - return ( <> {/* TODO: Build dynamic breadcrumbs */} {/** Disabled and set invisible when undefined to preserve layout height */} - - - {location.pathname.includes("opportunity") && ( - - )} - {location.pathname.includes("user") && ( - - )} - - {!location.pathname.includes("user") && ( - - )} + {breadcrumbs?.map(breadcrumb => { + if (breadcrumb.component) return breadcrumb.component; + return ( + + ); + })} @@ -119,8 +83,8 @@ export default function Hero({ navigation, icons, title, description, tags, chil )} - {/* TODO: Show "Opportunities" or "Campaigns" according to the page */} - {!location?.pathname.includes("user") && ( + {/* TODO: Move this outside the Hero component */} + {/* {!location?.pathname.includes("user") && ( @@ -150,20 +114,25 @@ export default function Hero({ navigation, icons, title, description, tags, chil - )} + )} */} - {!!tabs && ( - - {tabs?.map(tab => ( - - ))} - - )} + + {!!tabs && ( + + {tabs?.map(tab => ( + + ))} + + )} +
{children}
); diff --git a/src/components/element/Tag.tsx b/src/components/element/Tag.tsx index d940a69e..275bd353 100644 --- a/src/components/element/Tag.tsx +++ b/src/components/element/Tag.tsx @@ -1,4 +1,5 @@ import type { Opportunity, Token } from "@angleprotocol/merkl-api"; +import type { Chain } from "@merkl/api"; import { Button, Divider, Dropdown, Group, Hash, Icon, PrimitiveTag, Text } from "dappkit"; import type { ButtonProps } from "dappkit"; import { type Action, actions } from "src/config/actions"; @@ -78,7 +79,7 @@ export default function Tag({ type, value, ...props }: - @@ -110,7 +111,7 @@ export default function Tag({ type, value, ...props }: {action?.description} - @@ -148,7 +149,7 @@ export default function Tag({ type, value, ...props }: {/* {token?.description} */} - @@ -192,11 +193,11 @@ export default function Tag({ type, value, ...props }: {/* {token?.description} */} - - @@ -229,11 +230,10 @@ export default function Tag({ type, value, ...props }: {value?.name} - + + {/* {token?.description} */} - {/* {token?.description} */} - ); diff --git a/src/components/element/leaderboard/LeaderboardLibrary.tsx b/src/components/element/leaderboard/LeaderboardLibrary.tsx index 6c6ddd23..5a26b522 100644 --- a/src/components/element/leaderboard/LeaderboardLibrary.tsx +++ b/src/components/element/leaderboard/LeaderboardLibrary.tsx @@ -1,6 +1,6 @@ import { Text } from "dappkit"; import { useMemo } from "react"; -import type { DummyLeaderboard } from "src/routes/_merkl.opportunity.$chain.$type.$id.leaderboard"; +import type { DummyLeaderboard } from "src/routes/_merkl.opportunities.$chain.$type.$id.leaderboard"; import { LeaderboardTable } from "./LeaderboardTable"; import LeaderboardTableRow from "./LeaderboardTableRow"; diff --git a/src/components/element/opportunity/OpportunityButton.tsx b/src/components/element/opportunity/OpportunityButton.tsx index aa31be12..3789558f 100644 --- a/src/components/element/opportunity/OpportunityButton.tsx +++ b/src/components/element/opportunity/OpportunityButton.tsx @@ -1,4 +1,4 @@ -import type { Opportunity } from "@angleprotocol/merkl-api"; +import type { Opportunity } from "@merkl/api"; import { Button, Icons } from "dappkit"; import { blockEvent } from "packages/dappkit/src/utils/event"; import useOpportunity from "src/hooks/resources/useOpportunity"; diff --git a/src/components/element/opportunity/OpportunityFilters.tsx b/src/components/element/opportunity/OpportunityFilters.tsx index eaf92a4a..a7f826c8 100644 --- a/src/components/element/opportunity/OpportunityFilters.tsx +++ b/src/components/element/opportunity/OpportunityFilters.tsx @@ -1,4 +1,4 @@ -import type { Chain } from "@angleprotocol/merkl-api"; +import type { Chain } from "@merkl/api"; import { Form } from "@remix-run/react"; import { Group, Icon, Input, Select } from "dappkit/src"; import { useMemo, useState } from "react"; diff --git a/src/components/element/opportunity/OpportunityLibrary.tsx b/src/components/element/opportunity/OpportunityLibrary.tsx index 92977713..72eef6e6 100644 --- a/src/components/element/opportunity/OpportunityLibrary.tsx +++ b/src/components/element/opportunity/OpportunityLibrary.tsx @@ -1,4 +1,4 @@ -import type { Chain, Opportunity } from "@angleprotocol/merkl-api"; +import type { Chain, Opportunity } from "@merkl/api"; import { Group, type Order } from "dappkit"; import { useMemo } from "react"; import useSearchParamState from "src/hooks/filtering/useSearchParamState"; diff --git a/src/components/element/opportunity/OpportunityTableRow.tsx b/src/components/element/opportunity/OpportunityTableRow.tsx index c113262b..ffe420d4 100644 --- a/src/components/element/opportunity/OpportunityTableRow.tsx +++ b/src/components/element/opportunity/OpportunityTableRow.tsx @@ -1,4 +1,4 @@ -import type { Opportunity } from "@angleprotocol/merkl-api"; +import type { Opportunity } from "@merkl/api"; import { Link } from "@remix-run/react"; import { Group } from "dappkit"; import { Icons } from "dappkit"; diff --git a/src/components/element/participate/Participate.tsx b/src/components/element/participate/Participate.tsx index 4f813249..68740daf 100644 --- a/src/components/element/participate/Participate.tsx +++ b/src/components/element/participate/Participate.tsx @@ -1,4 +1,4 @@ -import type { Opportunity } from "@angleprotocol/merkl-api"; +import type { Opportunity } from "@merkl/api"; import { Box, Button, Input, Space, Text } from "dappkit"; export type ParticipateProps = { diff --git a/src/components/element/protocol/ProtocolFilters.tsx b/src/components/element/protocol/ProtocolFilters.tsx new file mode 100644 index 00000000..a9913533 --- /dev/null +++ b/src/components/element/protocol/ProtocolFilters.tsx @@ -0,0 +1,44 @@ +import type { Chain } from "@merkl/api"; +import { Form } from "@remix-run/react"; +import { Group, Icon, Input } from "dappkit/src"; +import { useState } from "react"; +import useSearchParamState from "src/hooks/filtering/useSearchParamState"; + +const filters = ["search"] as const; +type ProtocolFilter = (typeof filters)[number]; + +export type OpportunityFilterProps = { + only?: ProtocolFilter[]; + chains?: Chain[]; + exclude?: ProtocolFilter[]; +}; + +export default function ProtocolFilters(_props: OpportunityFilterProps) { + const [search, setSearch] = useSearchParamState( + "search", + v => v, + v => v, + ); + const [innerSearch, setInnerSearch] = useState(search ?? ""); + + function onSearchSubmit() { + if (!innerSearch || innerSearch === search) return; + + setSearch(innerSearch); + } + + return ( + +
+ } + onClick={onSearchSubmit} + placeholder="Search" + /> +
+
+ ); +} diff --git a/src/components/element/protocol/ProtocolLibrary.tsx b/src/components/element/protocol/ProtocolLibrary.tsx new file mode 100644 index 00000000..1ce9b1eb --- /dev/null +++ b/src/components/element/protocol/ProtocolLibrary.tsx @@ -0,0 +1,28 @@ +import type { Protocol } from "@merkl/api"; +import { Group } from "dappkit"; +import { useMemo } from "react"; +import OpportunityPagination from "../opportunity/OpportunityPagination"; +import ProtocolFilters from "./ProtocolFilters"; +import { ProtocolTable } from "./ProtocolTable"; +import ProtocolTableRow from "./ProtocolTableRow"; + +export type ProtocolLibraryProps = { + protocols: Protocol[]; + count?: number; +}; + +export default function ProtocolLibrary({ protocols, count }: ProtocolLibraryProps) { + const rows = useMemo(() => protocols?.map(p => ), [protocols]); + + return ( + } + header={ + + + + }> + {rows} + + ); +} diff --git a/src/components/element/protocol/ProtocolTable.tsx b/src/components/element/protocol/ProtocolTable.tsx new file mode 100644 index 00000000..400c39b5 --- /dev/null +++ b/src/components/element/protocol/ProtocolTable.tsx @@ -0,0 +1,11 @@ +import { createTable } from "dappkit"; + +export const [ProtocolTable, ProtocolRow, protocolColumns] = createTable({ + protocol: { + name: "PROTOCOL", + size: "minmax(350px,1fr)", + compact: "1fr", + className: "justify-start", + main: true, + }, +}); diff --git a/src/components/element/protocol/ProtocolTableRow.tsx b/src/components/element/protocol/ProtocolTableRow.tsx new file mode 100644 index 00000000..84560370 --- /dev/null +++ b/src/components/element/protocol/ProtocolTableRow.tsx @@ -0,0 +1,39 @@ +import type { Protocol } from "@merkl/api"; +import { Link } from "@remix-run/react"; +import { Group, Icon } from "dappkit"; +import type { BoxProps } from "dappkit"; +import { Title } from "dappkit"; +import { mergeClass } from "dappkit"; +import type { TagTypes } from "../Tag"; +import { ProtocolRow } from "./ProtocolTable"; + +export type ProtocolTableRowProps = { + hideTags?: (keyof TagTypes)[]; + protocol: Protocol; +} & BoxProps; + +export default function ProtocolTableRow({ hideTags, protocol, className, ...props }: OpportunityTableRowProps) { + return ( + + + + + <Icon src={protocol.icon} /> + {protocol.name} + + +
+ } + /> + + ); +} diff --git a/src/components/element/rewards/ClaimRewardsChainTableRow.tsx b/src/components/element/rewards/ClaimRewardsChainTableRow.tsx index f9249b04..a338f532 100644 --- a/src/components/element/rewards/ClaimRewardsChainTableRow.tsx +++ b/src/components/element/rewards/ClaimRewardsChainTableRow.tsx @@ -1,4 +1,4 @@ -import type { Reward } from "@angleprotocol/merkl-api"; +import type { Reward } from "@merkl/api"; import { Icon, Space, Text, Value } from "dappkit"; import TransactionButton from "packages/dappkit/src/components/dapp/TransactionButton"; import Collapsible from "packages/dappkit/src/components/primitives/Collapsible"; diff --git a/src/components/element/rewards/ClaimRewardsLibrary.tsx b/src/components/element/rewards/ClaimRewardsLibrary.tsx index 05e85636..17af919a 100644 --- a/src/components/element/rewards/ClaimRewardsLibrary.tsx +++ b/src/components/element/rewards/ClaimRewardsLibrary.tsx @@ -1,4 +1,4 @@ -import type { Reward } from "@angleprotocol/merkl-api"; +import type { Reward } from "@merkl/api"; import { Group } from "dappkit"; import { ClaimRewardsChainTable } from "./ClaimRewardsChainTable"; import ClaimRewardsChainTableRow from "./ClaimRewardsChainTableRow"; diff --git a/src/components/element/rewards/ClaimRewardsTokenTableRow.tsx b/src/components/element/rewards/ClaimRewardsTokenTableRow.tsx index 76bf9551..0cc8d358 100644 --- a/src/components/element/rewards/ClaimRewardsTokenTableRow.tsx +++ b/src/components/element/rewards/ClaimRewardsTokenTableRow.tsx @@ -1,4 +1,4 @@ -import type { Reward } from "@angleprotocol/merkl-api"; +import type { Reward } from "@merkl/api"; import { Checkbox, Divider, type GetSet, Group, Icon, Space } from "dappkit"; import Collapsible from "packages/dappkit/src/components/primitives/Collapsible"; import { type PropsWithChildren, useMemo, useState } from "react"; diff --git a/src/components/element/token/Token.tsx b/src/components/element/token/Token.tsx index d45e6d89..7bcfc26e 100644 --- a/src/components/element/token/Token.tsx +++ b/src/components/element/token/Token.tsx @@ -1,4 +1,4 @@ -import type { Token as TokenType } from "@angleprotocol/merkl-api"; +import type { Token as TokenType } from "@merkl/api"; import { Button, Dropdown, Icon, Value } from "packages/dappkit/src"; import { useMemo } from "react"; import TokenTooltip from "./TokenTooltip"; diff --git a/src/components/element/token/TokenFilters.tsx b/src/components/element/token/TokenFilters.tsx new file mode 100644 index 00000000..a9913533 --- /dev/null +++ b/src/components/element/token/TokenFilters.tsx @@ -0,0 +1,44 @@ +import type { Chain } from "@merkl/api"; +import { Form } from "@remix-run/react"; +import { Group, Icon, Input } from "dappkit/src"; +import { useState } from "react"; +import useSearchParamState from "src/hooks/filtering/useSearchParamState"; + +const filters = ["search"] as const; +type ProtocolFilter = (typeof filters)[number]; + +export type OpportunityFilterProps = { + only?: ProtocolFilter[]; + chains?: Chain[]; + exclude?: ProtocolFilter[]; +}; + +export default function ProtocolFilters(_props: OpportunityFilterProps) { + const [search, setSearch] = useSearchParamState( + "search", + v => v, + v => v, + ); + const [innerSearch, setInnerSearch] = useState(search ?? ""); + + function onSearchSubmit() { + if (!innerSearch || innerSearch === search) return; + + setSearch(innerSearch); + } + + return ( + +
+ } + onClick={onSearchSubmit} + placeholder="Search" + /> +
+
+ ); +} diff --git a/src/components/element/token/TokenLibrary.tsx b/src/components/element/token/TokenLibrary.tsx new file mode 100644 index 00000000..33b760fb --- /dev/null +++ b/src/components/element/token/TokenLibrary.tsx @@ -0,0 +1,31 @@ +import type { Token } from "@merkl/api"; +import { Group } from "dappkit"; +import { useMemo } from "react"; +import OpportunityPagination from "../opportunity/OpportunityPagination"; +import ProtocolFilters from "./TokenFilters"; +import { TokenTable } from "./TokenTable"; +import TokenTableRow from "./TokenTableRow"; + +export type TokenLibraryProps = { + tokens: Token[]; + count?: number; +}; + +export default function TokenLibrary({ tokens, count }: TokenLibraryProps) { + const rows = useMemo( + () => tokens?.map(t => ), + [tokens], + ); + + return ( + } + header={ + + + + }> + {rows} + + ); +} diff --git a/src/components/element/token/TokenTable.tsx b/src/components/element/token/TokenTable.tsx new file mode 100644 index 00000000..c79a5dc2 --- /dev/null +++ b/src/components/element/token/TokenTable.tsx @@ -0,0 +1,17 @@ +import { createTable } from "dappkit"; + +export const [TokenTable, TokenRow, tokenColumns] = createTable({ + token: { + name: "TOKEN", + size: "minmax(350px,1fr)", + compact: "1fr", + className: "justify-start", + main: true, + }, + price: { + name: "PRICE", + size: "minmax(min-content,150px)", + compactSize: "minmax(min-content,1fr)", + className: "justify-end", + }, +}); diff --git a/src/components/element/token/TokenTableRow.tsx b/src/components/element/token/TokenTableRow.tsx new file mode 100644 index 00000000..45f2ed5e --- /dev/null +++ b/src/components/element/token/TokenTableRow.tsx @@ -0,0 +1,48 @@ +import type { Token } from "@merkl/api"; +import { Link } from "@remix-run/react"; +import { Button, Group, Icon, Value } from "dappkit"; +import type { BoxProps } from "dappkit"; +import { Title } from "dappkit"; +import { mergeClass } from "dappkit"; +import type { TagTypes } from "../Tag"; +import { TokenRow } from "./TokenTable"; + +export type TokenTableRowProps = { + hideTags?: (keyof TagTypes)[]; + token: Token; +} & BoxProps; + +export default function TokenTableRow({ hideTags, token, className, ...props }: TokenTableRowProps) { + return ( + + + + + <Icon src={token.icon} /> + {token.name} + + +
+ } + priceColumn={ + + + + } + /> + + ); +} diff --git a/src/components/element/token/TokenTooltip.tsx b/src/components/element/token/TokenTooltip.tsx index 397c35ed..b688b087 100644 --- a/src/components/element/token/TokenTooltip.tsx +++ b/src/components/element/token/TokenTooltip.tsx @@ -1,4 +1,4 @@ -import type { Token } from "@angleprotocol/merkl-api"; +import type { Token } from "@merkl/api"; import { Button, Divider, Group, Hash, Icon, Text, Title } from "packages/dappkit/src"; export type TokenTooltipProps = { diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 6dba0411..4898ce24 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -2,14 +2,13 @@ import { Button, Container, Dropdown, Group, Icon, WalletButton, useTheme } from import { Image } from "dappkit"; import customerDarkLogo from "src/customer/assets/images/customer-dark-logo.svg"; import customerLogo from "src/customer/assets/images/customer-logo.svg"; -import SearchBar from "../element/functions/SearchBar"; import { motion } from "framer-motion"; import config from "merkl.config"; -import { useState } from "react"; +import { useWalletContext } from "packages/dappkit/src/context/Wallet.context"; +import { useMemo, useState } from "react"; import { useMediaQuery } from "react-responsive"; import SCREENS from "../../../packages/dappkit/src/constants/SCREENS.json"; -import SwitchMode from "../element/SwitchMode"; import { LayerMenu } from "./LayerMenu"; const container = { @@ -31,11 +30,29 @@ const item = { }; export default function Header() { - const { mode } = useTheme(); + const { mode, toggleMode } = useTheme(); + const { address: user } = useWalletContext(); const [open, setOpen] = useState(false); const mdScreens = useMediaQuery({ maxWidth: SCREENS.lg }); const smScreens = useMediaQuery({ maxWidth: SCREENS.md }); + const canSwitchModes = useMemo(() => !(!config.modes || config.modes?.length === 1), []); + + const routes = useMemo(() => { + const { homepage, ...rest } = config.routes; + + return Object.assign( + { homepage }, + { + dashboard: { + icon: "RiPlanetFill", + route: user ? `/users/${user}` : "/users", + key: crypto.randomUUID(), + }, + }, + rest, + ); + }, [user]); return ( } + content={} className="flex gap-sm md:gap-lg items-center"> ) : ( - - ); - })} - - - - + {!mdScreens && + Object.entries(routes) + .filter(([key]) => !["homepage", "privacy", "terms"].includes(key)) + .map(([key, { route }]) => { + return ( + + ); + })} + {canSwitchModes && ( + )} {!smScreens && } diff --git a/src/config/actions.ts b/src/config/actions.ts index 1735ae40..772acc21 100644 --- a/src/config/actions.ts +++ b/src/config/actions.ts @@ -1,4 +1,4 @@ -import type { Opportunity } from "@angleprotocol/merkl-api"; +import type { Opportunity } from "@merkl/api"; import type { IconProps } from "dappkit"; export const actions = { diff --git a/src/config/status.ts b/src/config/status.ts index 8e771859..1fbb15ca 100644 --- a/src/config/status.ts +++ b/src/config/status.ts @@ -1,4 +1,4 @@ -import type { Opportunity } from "@angleprotocol/merkl-api"; +import type { Opportunity } from "@merkl/api"; import type { IconProps } from "dappkit"; export const statuses = { diff --git a/src/entry.server.tsx b/src/entry.server.tsx index 1f5a6f8b..e343283c 100644 --- a/src/entry.server.tsx +++ b/src/entry.server.tsx @@ -83,6 +83,7 @@ function handleBrowserRequest( ) { return new Promise((resolve, reject) => { let shellRendered = false; + const { pipe, abort } = renderToPipeableStream( , { diff --git a/src/hooks/resources/useCampaign.tsx b/src/hooks/resources/useCampaign.tsx index fef6c7b3..dcae2f51 100644 --- a/src/hooks/resources/useCampaign.tsx +++ b/src/hooks/resources/useCampaign.tsx @@ -1,4 +1,4 @@ -import type { Campaign, Opportunity } from "@angleprotocol/merkl-api"; +import type { Campaign, Opportunity } from "@merkl/api"; import { Bar } from "dappkit"; import moment from "moment"; import { Group, Text, Value } from "packages/dappkit/src"; diff --git a/src/hooks/resources/useOpportunity.tsx b/src/hooks/resources/useOpportunity.tsx index 571478ce..ef6f311e 100644 --- a/src/hooks/resources/useOpportunity.tsx +++ b/src/hooks/resources/useOpportunity.tsx @@ -1,4 +1,4 @@ -import type { Opportunity } from "@angleprotocol/merkl-api"; +import type { Opportunity } from "@merkl/api"; import { Icon } from "dappkit"; import { useMemo } from "react"; import type { TagType } from "src/components/element/Tag"; @@ -15,7 +15,7 @@ export default function useOpportunity(opportunity: Opportunity) { }, [opportunity]); const link = useMemo( - () => `/opportunity/${opportunity.chain?.name?.toLowerCase?.()}/${opportunity.type}/${opportunity.identifier}`, + () => `/opportunities/${opportunity.chain?.name?.toLowerCase?.()}/${opportunity.type}/${opportunity.identifier}`, [opportunity], ); diff --git a/src/hooks/useMerklSearch.tsx b/src/hooks/useMerklSearch.tsx index 75effac1..19e83168 100644 --- a/src/hooks/useMerklSearch.tsx +++ b/src/hooks/useMerklSearch.tsx @@ -1,4 +1,4 @@ -import type { Chain, Opportunity, Protocol, Token } from "@angleprotocol/merkl-api"; +import type { Chain, Opportunity, Protocol, Token } from "@merkl/api"; import { useCallback, useEffect, useMemo, useState } from "react"; import { api } from "src/api/index.client"; diff --git a/src/routes/_merkl.action.$action.(opportunities).tsx b/src/routes/_merkl.actions.$action.(opportunities).tsx similarity index 96% rename from src/routes/_merkl.action.$action.(opportunities).tsx rename to src/routes/_merkl.actions.$action.(opportunities).tsx index 0d22b4cb..853d999b 100644 --- a/src/routes/_merkl.action.$action.(opportunities).tsx +++ b/src/routes/_merkl.actions.$action.(opportunities).tsx @@ -11,7 +11,7 @@ export async function loader({ params: { action: _action }, request }: LoaderFun const action = getAction(_action ?? ""); if (!action) throw new Error("Unknown action"); - const { opportunities, count } = await OpportunityService.getMany({ action }); + const { opportunities, count } = await OpportunityService.getManyFromRequest(request, { action }); const chains = await ChainService.getAll(); return json({ opportunities, chains, count }); diff --git a/src/routes/_merkl.action.$action.tsx b/src/routes/_merkl.actions.$action.tsx similarity index 93% rename from src/routes/_merkl.action.$action.tsx rename to src/routes/_merkl.actions.$action.tsx index 4fc39661..a1abc487 100644 --- a/src/routes/_merkl.action.$action.tsx +++ b/src/routes/_merkl.actions.$action.tsx @@ -24,7 +24,7 @@ export default function Index() { tabs={[ { label: "Opportunities", - link: `/action/${action.label?.toLowerCase()}`, + link: `/actions/${action.label?.toLowerCase()}`, }, ]}> diff --git a/src/routes/_merkl.campaign.$id.tsx b/src/routes/_merkl.campaign.$id.tsx deleted file mode 100644 index f669caba..00000000 --- a/src/routes/_merkl.campaign.$id.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Group } from "dappkit"; - -export default function Index() { - return ( - - OP - - ); -} diff --git a/src/routes/_merkl.chain.$id.(opportunities).tsx b/src/routes/_merkl.chains.$id.(opportunities).tsx similarity index 93% rename from src/routes/_merkl.chain.$id.(opportunities).tsx rename to src/routes/_merkl.chains.$id.(opportunities).tsx index 66d54621..1f144482 100644 --- a/src/routes/_merkl.chain.$id.(opportunities).tsx +++ b/src/routes/_merkl.chains.$id.(opportunities).tsx @@ -7,7 +7,9 @@ import OpportunityLibrary from "src/components/element/opportunity/OpportunityLi export async function loader({ params: { id: chainId }, request }: LoaderFunctionArgs) { const chain = await ChainService.get({ search: chainId }); - const { opportunities, count } = await OpportunityService.getMany({ chainId: chain.id.toString() }); + const { opportunities, count } = await OpportunityService.getManyFromRequest(request, { + chainId: chain.id.toString(), + }); return json({ opportunities, count }); } diff --git a/src/routes/_merkl.chain.$id.tsx b/src/routes/_merkl.chains.$id.tsx similarity index 100% rename from src/routes/_merkl.chain.$id.tsx rename to src/routes/_merkl.chains.$id.tsx diff --git a/src/routes/_merkl.opportunity.$chain.$type.$id.(overview).tsx b/src/routes/_merkl.opportunities.$chain.$type.$id.(overview).tsx similarity index 87% rename from src/routes/_merkl.opportunity.$chain.$type.$id.(overview).tsx rename to src/routes/_merkl.opportunities.$chain.$type.$id.(overview).tsx index 5992cb86..0b04c6b9 100644 --- a/src/routes/_merkl.opportunity.$chain.$type.$id.(overview).tsx +++ b/src/routes/_merkl.opportunities.$chain.$type.$id.(overview).tsx @@ -1,6 +1,5 @@ -import { Group } from "@ariakit/react"; import { useOutletContext } from "@remix-run/react"; -import { Space } from "packages/dappkit/src"; +import { Container, Space } from "packages/dappkit/src"; import CampaignLibrary from "src/components/element/campaign/CampaignLibrary"; import { ErrorContent } from "src/components/layout/ErrorContent"; import type { OutletContextOpportunity } from "./_merkl.opportunity.$chain.$type.$id"; @@ -9,7 +8,7 @@ export default function Index() { const { opportunity } = useOutletContext(); return ( - + {/* */} @@ -17,7 +16,7 @@ export default function Index() { */} {/* */} - + ); } diff --git a/src/routes/_merkl.opportunities.$chain.$type.$id.analytics.tsx b/src/routes/_merkl.opportunities.$chain.$type.$id.analytics.tsx new file mode 100644 index 00000000..a6cedacd --- /dev/null +++ b/src/routes/_merkl.opportunities.$chain.$type.$id.analytics.tsx @@ -0,0 +1,10 @@ +import { type LoaderFunctionArgs, json } from "@remix-run/node"; +import { Text } from "dappkit/src"; + +export async function loader({ params }: LoaderFunctionArgs) { + return json({ chain: params.chain }); +} + +export default function Index() { + return Analytics; +} diff --git a/src/routes/_merkl.opportunity.$chain.$type.$id.leaderboard.tsx b/src/routes/_merkl.opportunities.$chain.$type.$id.leaderboard.tsx similarity index 94% rename from src/routes/_merkl.opportunity.$chain.$type.$id.leaderboard.tsx rename to src/routes/_merkl.opportunities.$chain.$type.$id.leaderboard.tsx index b4419141..01d28af1 100644 --- a/src/routes/_merkl.opportunity.$chain.$type.$id.leaderboard.tsx +++ b/src/routes/_merkl.opportunities.$chain.$type.$id.leaderboard.tsx @@ -1,6 +1,6 @@ import type { LoaderFunctionArgs } from "@remix-run/node"; import { json, useLoaderData } from "@remix-run/react"; -import { Group, Text } from "packages/dappkit/src"; +import { Container, Group, Space, Text } from "packages/dappkit/src"; import Tooltip from "packages/dappkit/src/components/primitives/Tooltip"; import LeaderboardLibrary from "src/components/element/leaderboard/LeaderboardLibrary"; // import { ChainService } from "src/api/services/chain.service"; @@ -51,7 +51,8 @@ export default function Index() { const { leaderboard } = useLoaderData(); return ( - <> + + @@ -67,8 +68,8 @@ export default function Index() { 400k - + - + ); } diff --git a/src/routes/_merkl.opportunities.$chain.$type.$id.tsx b/src/routes/_merkl.opportunities.$chain.$type.$id.tsx new file mode 100644 index 00000000..67578d3b --- /dev/null +++ b/src/routes/_merkl.opportunities.$chain.$type.$id.tsx @@ -0,0 +1,89 @@ +import type { Opportunity } from "@angleprotocol/merkl-api"; +import { type LoaderFunctionArgs, type MetaFunction, json } from "@remix-run/node"; +import { Meta, Outlet, useLoaderData } from "@remix-run/react"; +import { useMemo } from "react"; +import { ChainService } from "src/api/services/chain.service"; +import { OpportunityService } from "src/api/services/opportunity.service"; +import Hero from "src/components/composite/Hero"; +import Tag from "src/components/element/Tag"; +import { ErrorHeading } from "src/components/layout/ErrorHeading"; +import useOpportunity from "src/hooks/resources/useOpportunity"; + +export async function loader({ params: { id, type, chain: chainId } }: LoaderFunctionArgs) { + if (!chainId || !id || !type) throw ""; + + const chain = await ChainService.get({ search: chainId }); + + const opportunity = await OpportunityService.getCampaignsByParams({ + chainId: chain.id, + type: type, + identifier: id, + }); + + return json({ opportunity }); +} + +export const meta: MetaFunction = ({ data, error }) => { + if (error) return [{ title: error }]; + return [{ title: `${data?.name} on Merkl` }]; +}; + +export type OutletContextOpportunity = { + opportunity: Opportunity; +}; + +export default function Index() { + const { opportunity } = useLoaderData(); + const { tags, description, link } = useOpportunity(opportunity); + + const styleName = useMemo(() => { + const spaced = opportunity?.name.split(" "); + + return spaced + .map(str => { + const key = str + crypto.randomUUID(); + if (!str.match(/[\p{Letter}\p{Mark}]+/gu)) + return [ + + {str} + , + ]; + if (str.includes("-")) + return str + .split("-") + .flatMap((s, i, arr) => [s, i !== arr.length - 1 && -]); + if (str.includes("/")) + return str + .split("/") + .flatMap((s, i, arr) => [s, i !== arr.length - 1 && /]); + return [{str}]; + }) + .flatMap((str, index, arr) => [str, index !== arr.length - 1 && " "]); + }, [opportunity]); + + return ( + <> + + ({ src: t.icon }))} + breadcrumbs={[ + { link: "/", name: "Opportunities" }, + { link: "/", name: opportunity.name }, + ]} + title={styleName} + description={description} + tabs={[ + { label: "Overview", link }, + { label: "Leaderboard", link: `${link}/leaderboard` }, + ]} + tags={tags.map(tag => )} + opportunity={opportunity}> + + + + ); +} + +export function ErrorBoundary() { + return ; +} diff --git a/src/routes/_merkl.opportunity.$chain.$type.$id.tsx b/src/routes/_merkl.opportunity.$chain.$type.$id.tsx index e08112a1..67578d3b 100644 --- a/src/routes/_merkl.opportunity.$chain.$type.$id.tsx +++ b/src/routes/_merkl.opportunity.$chain.$type.$id.tsx @@ -20,8 +20,6 @@ export async function loader({ params: { id, type, chain: chainId } }: LoaderFun identifier: id, }); - if (!opportunity) throw "DAZZ"; - return json({ opportunity }); } @@ -42,7 +40,7 @@ export default function Index() { const spaced = opportunity?.name.split(" "); return spaced - .map((str, index) => { + .map(str => { const key = str + crypto.randomUUID(); if (!str.match(/[\p{Letter}\p{Mark}]+/gu)) return [ @@ -68,7 +66,10 @@ export default function Index() { ({ src: t.icon }))} - navigation={{ label: "Back to opportunities", link: "/" }} + breadcrumbs={[ + { link: "/", name: "Opportunities" }, + { link: "/", name: opportunity.name }, + ]} title={styleName} description={description} tabs={[ diff --git a/src/routes/_merkl.protocol.$id.(opportunities).tsx b/src/routes/_merkl.protocols.$id.(opportunities).tsx similarity index 95% rename from src/routes/_merkl.protocol.$id.(opportunities).tsx rename to src/routes/_merkl.protocols.$id.(opportunities).tsx index 8d4ea9f5..21e8e827 100644 --- a/src/routes/_merkl.protocol.$id.(opportunities).tsx +++ b/src/routes/_merkl.protocols.$id.(opportunities).tsx @@ -8,7 +8,7 @@ import OpportunityLibrary from "src/components/element/opportunity/OpportunityLi export async function loader({ params: { id }, request }: LoaderFunctionArgs) { const protocol = await ProtocolService.get({ id: id ?? "" }); - const { opportunities, count } = await OpportunityService.getMany({ mainProtocolId: id }); + const { opportunities, count } = await OpportunityService.getManyFromRequest(request, { mainProtocolId: id }); const chains = await ChainService.getAll(); return json({ opportunities, chains, count, protocol }); diff --git a/src/routes/_merkl.protocol.$id.tsx b/src/routes/_merkl.protocols.$id.tsx similarity index 77% rename from src/routes/_merkl.protocol.$id.tsx rename to src/routes/_merkl.protocols.$id.tsx index 29cdc6b8..cc5b51a5 100644 --- a/src/routes/_merkl.protocol.$id.tsx +++ b/src/routes/_merkl.protocols.$id.tsx @@ -15,13 +15,16 @@ export default function Index() { return ( diff --git a/src/routes/_merkl.protocols.(all).(protocols).tsx b/src/routes/_merkl.protocols.(all).(protocols).tsx new file mode 100644 index 00000000..694977ea --- /dev/null +++ b/src/routes/_merkl.protocols.(all).(protocols).tsx @@ -0,0 +1,22 @@ +import type { LoaderFunctionArgs } from "@remix-run/node"; +import { json, useLoaderData } from "@remix-run/react"; +import { Container, Space } from "packages/dappkit/src"; +import { ProtocolService } from "src/api/services/protocol.service"; +import ProtocolLibrary from "src/components/element/protocol/ProtocolLibrary"; + +export async function loader({ params: { id }, request }: LoaderFunctionArgs) { + const { protocols, count } = await ProtocolService.getManyFromRequest(request); + + return json({ protocols, count }); +} + +export default function Index() { + const { protocols, count } = useLoaderData(); + + return ( + + + + + ); +} diff --git a/src/routes/_merkl.protocols.(all).tsx b/src/routes/_merkl.protocols.(all).tsx new file mode 100644 index 00000000..c4c38afb --- /dev/null +++ b/src/routes/_merkl.protocols.(all).tsx @@ -0,0 +1,14 @@ +import { Outlet } from "@remix-run/react"; +import Hero from "src/components/composite/Hero"; + +export default function Index() { + return ( + + + + ); +} diff --git a/src/routes/_merkl.status.$status.(opportunities).tsx b/src/routes/_merkl.status.$status.(opportunities).tsx index 844218af..0b274ac3 100644 --- a/src/routes/_merkl.status.$status.(opportunities).tsx +++ b/src/routes/_merkl.status.$status.(opportunities).tsx @@ -10,7 +10,7 @@ export async function loader({ params: { status: _status }, request }: LoaderFun const status = getStatus(_status ?? ""); if (!status) throw new Error("Unknown status"); - const { opportunities, count } = await OpportunityService.getMany({ status }); + const { opportunities, count } = await OpportunityService.getManyFromRequest(request, { status }); const chains = await ChainService.getAll(); return json({ opportunities, chains, count }); diff --git a/src/routes/_merkl.status.$status.tsx b/src/routes/_merkl.status.$status.tsx index 944ed524..6aa5b50c 100644 --- a/src/routes/_merkl.status.$status.tsx +++ b/src/routes/_merkl.status.$status.tsx @@ -18,7 +18,10 @@ export default function Index() { return ( t.icon && t.icon !== "")?.icon }]} navigation={{ label: "Back to opportunities", link: "/" }} title={ diff --git a/src/routes/_merkl.tokens.(all).(tokens).tsx b/src/routes/_merkl.tokens.(all).(tokens).tsx new file mode 100644 index 00000000..a18e00ce --- /dev/null +++ b/src/routes/_merkl.tokens.(all).(tokens).tsx @@ -0,0 +1,22 @@ +import type { LoaderFunctionArgs } from "@remix-run/node"; +import { json, useLoaderData } from "@remix-run/react"; +import { Container, Space } from "packages/dappkit/src"; +import { TokenService } from "src/api/services/token.service"; +import TokenLibrary from "src/components/element/token/TokenLibrary"; + +export async function loader({ params: { id }, request }: LoaderFunctionArgs) { + const { tokens, count } = await TokenService.getManyFromRequest(request); + + return json({ tokens, count }); +} + +export default function Index() { + const { tokens, count } = useLoaderData(); + + return ( + + + + + ); +} diff --git a/src/routes/_merkl.tokens.(all).tsx b/src/routes/_merkl.tokens.(all).tsx new file mode 100644 index 00000000..e12d9d27 --- /dev/null +++ b/src/routes/_merkl.tokens.(all).tsx @@ -0,0 +1,14 @@ +import { Outlet } from "@remix-run/react"; +import Hero from "src/components/composite/Hero"; + +export default function Index() { + return ( + + + + ); +} diff --git a/src/routes/_merkl.user.$address.(rewards).tsx b/src/routes/_merkl.users.$address.(rewards).tsx similarity index 87% rename from src/routes/_merkl.user.$address.(rewards).tsx rename to src/routes/_merkl.users.$address.(rewards).tsx index aa0134ee..ad94dac3 100644 --- a/src/routes/_merkl.user.$address.(rewards).tsx +++ b/src/routes/_merkl.users.$address.(rewards).tsx @@ -7,7 +7,7 @@ import ClaimRewardsLibrary from "src/components/element/rewards/ClaimRewardsLibr export async function loader({ params: { address } }: LoaderFunctionArgs) { if (!address) throw ""; - const { data: rewards, ...res } = await api.v4.users({ address }).rewards.full.get({ query: {} }); + const { data: rewards } = await api.v4.users({ address }).rewards.full.get({ query: {} }); if (!rewards) throw ""; diff --git a/src/routes/_merkl.user.$address.claims.tsx b/src/routes/_merkl.users.$address.claims.tsx similarity index 100% rename from src/routes/_merkl.user.$address.claims.tsx rename to src/routes/_merkl.users.$address.claims.tsx diff --git a/src/routes/_merkl.user.$address.liquidity.tsx b/src/routes/_merkl.users.$address.liquidity.tsx similarity index 100% rename from src/routes/_merkl.user.$address.liquidity.tsx rename to src/routes/_merkl.users.$address.liquidity.tsx diff --git a/src/routes/_merkl.user.$address.tsx b/src/routes/_merkl.users.$address.tsx similarity index 87% rename from src/routes/_merkl.user.$address.tsx rename to src/routes/_merkl.users.$address.tsx index ebd63f51..7f06a44d 100644 --- a/src/routes/_merkl.user.$address.tsx +++ b/src/routes/_merkl.users.$address.tsx @@ -5,10 +5,14 @@ import Hero from "src/components/composite/Hero"; export default function Index() { const { address } = useParams(); - const [_isEditingAddress, setIsEditingAddress] = useState(false); + const [_isEditingAddress] = useState(false); return ( @@ -57,7 +61,7 @@ export default function Index() { Rewards ), - link: `/user/${address}`, + link: `/users/${address}`, }, { label: ( @@ -66,7 +70,7 @@ export default function Index() { Liquidity ), - link: `/user/${address}/liquidity`, + link: `/users/${address}/liquidity`, }, { label: ( @@ -75,7 +79,7 @@ export default function Index() { Claims ), - link: `/user/${address}/claims`, + link: `/users/${address}/claims`, }, ]}> diff --git a/src/routes/_merkl.users.(none).tsx b/src/routes/_merkl.users.(none).tsx new file mode 100644 index 00000000..b3884a92 --- /dev/null +++ b/src/routes/_merkl.users.(none).tsx @@ -0,0 +1,53 @@ +import { Outlet } from "@remix-run/react"; +import { Hash, Icon } from "dappkit"; +import { useWalletContext } from "packages/dappkit/src/context/Wallet.context"; +import { useState } from "react"; +import Hero from "src/components/composite/Hero"; + +export default function Index() { + const [_isEditingAddress] = useState(false); + const { address } = useWalletContext(); + + return ( + + {address} + + } + tabs={[ + { + label: ( + <> + + Rewards + + ), + link: `/users/${address}`, + }, + { + label: ( + <> + + Liquidity + + ), + link: `/users/${address}/liquidity`, + }, + { + label: ( + <> + + Claims + + ), + link: `/users/${address}/claims`, + }, + ]}> + + + ); +} diff --git a/src/shorthands.css b/src/shorthands.css deleted file mode 100644 index e69de29b..00000000 diff --git a/vite.config.ts b/vite.config.ts index 4379b744..d9e4450e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -15,6 +15,7 @@ export default defineConfig({ v3_relativeSplatPath: true, v3_throwAbortReason: true, }, + }), tsconfigPaths(), ecsstatic()