diff --git a/web/src/app/manage/Manage.tsx b/web/src/app/manage/Manage.tsx index 1aed57a9..742039dc 100644 --- a/web/src/app/manage/Manage.tsx +++ b/web/src/app/manage/Manage.tsx @@ -19,8 +19,7 @@ fAbioarvxMkYAsBAwg5Tmd5cipU8ZHxdmK47jqZWtpv export const Manage = () => { const mockApiData = [ { - id: "AdXkDnJpFKqZeoUygLvm5dp2b5JGVPz3rEWfGCtB5Kc2", - symbol: "iBTC", + id: "IBIT", name: "iShares Bitcoin Trust", aum: 15941890385, nav: 39.72, @@ -28,8 +27,7 @@ export const Manage = () => { 'url("https://api.glam.systems/image/EMAbk6kYhQbvtpqWyfvDPVJBvD5isMZvQT5aM4TyCAeG.png")' }, { - id: "AdXkDnJpFKqZeoUygLvm5dp2b5JGVPz3rEWfGCtB5Kc2", - symbol: "iETH", + id: "IETH", name: "iShares Ethereum Trust", aum: 15941890385, nav: 39.72, @@ -37,8 +35,7 @@ export const Manage = () => { 'url("https://api.glam.systems/image/yurUzfjdrUH2ujsWwQkFsv8eQJiJwgbHQFUZtf5yqoV.png")' }, { - id: "AdXkDnJpFKqZeoUygLvm5dp2b5JGVPz3rEWfGCtB5Kc2", - symbol: "iSOL", + id: "ISOL", name: "iShares Solana Trust", aum: 15941890385, nav: 39.72, @@ -46,8 +43,7 @@ export const Manage = () => { 'url("https://api.glam.systems/image/fAbioarvxMkYAsBAwg5Tmd5cipU8ZHxdmK47jqZWtpv.png")' }, { - id: "AdXkDnJpFKqZeoUygLvm5dp2b5JGVPz3rEWfGCtB5Kc2", - symbol: "iBONK", + id: "IBNK", name: "iShares Bonk Trust", aum: 15941890385, nav: 39.72, @@ -75,7 +71,7 @@ export const Manage = () => { >
-

{position.symbol}

+

{position.id}

{position.name}
diff --git a/web/src/app/products/SideActionBar.tsx b/web/src/app/products/SideActionBar.tsx index bef9d2e3..f9565c25 100644 --- a/web/src/app/products/SideActionBar.tsx +++ b/web/src/app/products/SideActionBar.tsx @@ -1,102 +1,92 @@ -import { - Button, - NumberInput, - RadioButton, - RadioButtonGroup, - Select -} from "@carbon/react"; - -import { Add } from "@carbon/icons-react"; -import { BN } from "@coral-xyz/anchor"; +import { Button, NumberInput, Select, RadioButton, RadioButtonGroup } from '@carbon/react'; import { PublicKey } from "@solana/web3.js"; -import { gray70Hover } from "@carbon/colors"; +import { BN } from "@coral-xyz/anchor"; +import { Add } from '@carbon/icons-react'; +import { gray70Hover } from '@carbon/colors'; +import { useState } from 'react'; import { useGlamProgramAccount } from "../glam/glam-data-access"; -import { useState } from "react"; type SideActionBarProps = { - type: "Subscribe" | "Redeem" | "Manage"; - fund: any; + type: 'Subscribe' | 'Redeem' | 'Manage'; + fund: any, primayButtonFunction: () => void; }; const ASSETS_MAP: { [key: string]: string } = { - USDC: "8zGuJQqwhZafTah7Uc7Z4tXRnguqkn5KLFAP8oV6PHe2", - WSOL: "So11111111111111111111111111111111111111112", - BTC: "3BZPwbcqB5kKScF3TEXxwNfx5ipV13kbRVDvfVp5c6fv" -}; + "USDC": "8zGuJQqwhZafTah7Uc7Z4tXRnguqkn5KLFAP8oV6PHe2", + "WSOL": "So11111111111111111111111111111111111111112", + "BTC": "3BZPwbcqB5kKScF3TEXxwNfx5ipV13kbRVDvfVp5c6fv", +} + const SubscribeActionBar = ({ fund, - subscribe -}: { - fund: any; - subscribe: any; + subscribe, +} : { + fund: any, + subscribe: any, }) => { + const [asset, setAsset] = useState("USDC"); const [amount, setAmount] = useState(0); - return ( - <> - - { - if (value) { - setAmount(value as number); + return (<> + + + { + if (value) { + setAmount(value as number); + return; + } + if (direction === 'up') { + setAmount(amount + 1); + } else { + if (amount === 0) { return; } - if (direction === "up") { - setAmount(amount + 1); - } else { - if (amount === 0) { - return; - } - setAmount(amount - 1); - } - }} - step={1} - /> + setAmount(amount - 1); + } + }} + step={1} + />
- - ); + ); }; -const RedeemActionBar = ({ fund, redeem }: { fund: any; redeem: any }) => { +const RedeemActionBar = ({ + fund, + redeem, +} : { + fund: any, + redeem: any, +}) => { + const [amount, setAmount] = useState(0); const [inKind, setInKind] = useState(1); - return ( - <> - { - if (value) { - setAmount(value as number); + return (<> + + { + if (value) { + setAmount(value as number); + return; + } + if (direction === 'up') { + setAmount(amount + 1); + } else { + if (amount === 0) { return; } - if (direction === "up") { - setAmount(amount + 1); - } else { - if (amount === 0) { - return; - } - setAmount(amount - 1); - } - }} - step={1} - /> - { - setInKind(val as number); - }} - orientation="vertical" - > - - - -
+ setAmount(amount - 1); + } + }} + step={1} + /> + { setInKind(val as number); } } + orientation="vertical" + > + + + +
- - ); + ); }; -const ManageActionBar = ({ fund }: { fund: any }) => { +const ManageActionBar = ({ + fund, +} : { + fund: any, +}) => { + const [app, setApp] = useState("Drift"); const [asset, setAsset] = useState("USDC"); const [amount, setAmount] = useState(0); - return ( - <> - + return (<> + + - - { - if (value) { - setAmount(value as number); + + { + if (value) { + setAmount(value as number); + return; + } + if (direction === 'up') { + setAmount(amount + 1); + } else { + if (amount === 0) { return; } - if (direction === "up") { - setAmount(amount + 1); - } else { - if (amount === 0) { - return; - } - setAmount(amount - 1); - } - }} - step={1} - /> + setAmount(amount - 1); + } + }} + step={1} + />
- - ); + + ); }; + export const SideActionBar = ({ type, fund, - primayButtonFunction + primayButtonFunction, }: SideActionBarProps) => { const grayStyle = { color: gray70Hover, - fontSize: "14px", - lineHeight: "18px" + fontSize: '14px', + lineHeight: '18px', }; const { subscribe, redeem } = useGlamProgramAccount({ fundKey: fund.key }); return ( -
- {type === "Subscribe" && ( - - )} - {type === "Redeem" && } - {type === "Manage" && } +
+ {type === 'Subscribe' && } + {type === 'Redeem' && } + {type === 'Manage' && }
); }; diff --git a/web/src/app/products/product-page.tsx b/web/src/app/products/product-page.tsx index 832e6d78..21e2e2a9 100644 --- a/web/src/app/products/product-page.tsx +++ b/web/src/app/products/product-page.tsx @@ -1,5 +1,6 @@ import "@carbon/charts-react/styles.css"; +import { PublicKey } from "@solana/web3.js"; import { IconArrowDownRight, IconArrowUpRight } from "@tabler/icons-react"; import { LineChart, ScaleTypes } from "@carbon/charts-react"; import { @@ -12,45 +13,46 @@ import { Tile } from "@carbon/react"; import { formatNumber, formatPercent } from "../utils/format-number"; +import { useParams } from "react-router-dom"; +import { useWallet } from "@solana/wallet-adapter-react"; + +import { SideActionBar } from "./SideActionBar"; +import { gray70Hover } from "@carbon/colors"; + import { - getAum, - getTotalShares, + useGlamProgramAccount, useFundPerfChartData, - useGlamProgramAccount + getAum, + getTotalShares } from "../glam/glam-data-access"; - import { ExplorerLink } from "../cluster/cluster-ui"; -import { PublicKey } from "@solana/web3.js"; -import { SideActionBar } from "./SideActionBar"; import { ellipsify } from "../ui/ui-layout"; -import { gray70Hover } from "@carbon/colors"; -import { useParams } from "react-router-dom"; -import { useWallet } from "@solana/wallet-adapter-react"; -export default function ProductPage() { - class FundModel { - key: PublicKey; - data: any; +class FundModel { + key: PublicKey; + data: any; - constructor(key: PublicKey, data: any) { - this.key = key; - this.data = data || {}; - } + constructor(key: PublicKey, data: any) { + this.key = key; + this.data = data || {}; + } - getImageUrl() { - const pubkey = - this.data?.shareClasses[0].toBase58() || - "1111111111111111111111111111111111"; - return `https://api.glam.systems/image/${pubkey}.png`; - } + getImageUrl() { + const pubkey = + this.data?.shareClasses[0].toBase58() || + "1111111111111111111111111111111111"; + return `https://api.glam.systems/image/${pubkey}.png`; + } - getManagementFee() { - return this.data?.shareClassesMetadata[0].feeManagement / 1_000_000.0; - } - getPerformanceFee() { - return this.data?.shareClassesMetadata[0].feePerformance / 1_000_000.0; - } + getManagementFee() { + return this.data?.shareClassesMetadata[0].feeManagement / 1_000_000.0; } + getPerformanceFee() { + return this.data?.shareClassesMetadata[0].feePerformance / 1_000_000.0; + } +} + +export default function ProductPage() { const grayStyle = { color: gray70Hover, fontSize: "14px", @@ -58,12 +60,16 @@ export default function ProductPage() { }; // retrieve the publicKey from the URL - const { id } = useParams(); - const { publicKey } = useWallet(); + let { id } = useParams(); // fetch the fund, for now we default to 2ex... const defaultFund = "AdXkDnJpFKqZeoUygLvm5dp2b5JGVPz3rEWfGCtB5Kc2"; - const fundKey = new PublicKey(id ?? defaultFund); + let fundKey = new PublicKey(defaultFund); + try { + fundKey = new PublicKey(id || defaultFund); + } catch (_e) { + // pass + } const fundId = fundKey.toString(); const fundPerfChartData = useFundPerfChartData(fundId) || [ @@ -72,14 +78,18 @@ export default function ProductPage() { ]; const { account } = useGlamProgramAccount({ fundKey }); + if (account.isLoading) { + return ""; //spinner + } const data = account.data; - const fundModel = new FundModel(fundKey, data); + const fundModel = new FundModel(fundKey, account.data); - const isManager = publicKey?.toString() === data?.manager?.toString(); + const { publicKey } = useWallet(); + const isManager = publicKey?.toString() == data?.manager?.toString(); - const aum = getAum(data?.treasury?.toString() || ""); - const totalShares = getTotalShares(data?.shareClasses[0] || fundKey); + const aum = getAum(data!.treasury!.toString()); + const totalShares = getTotalShares(data!.shareClasses[0]); const fund = { id: fundId, @@ -100,12 +110,8 @@ export default function ProductPage() { // "24HourNetInflowChange": 0.0089, // will optimize looping later once we have all the necessary data fees: { - management: - ((data?.shareClassesMetadata || [])[0].feeManagement || 0) / - 1_000_000.0, - performance: - ((data?.shareClassesMetadata || [])[0].feePerformance || 0) / - 1_000_000.0, + management: fundModel.getManagementFee(), + performance: fundModel.getPerformanceFee(), subscription: 0.0, redemption: 0.0 }, @@ -153,7 +159,7 @@ export default function ProductPage() { return (
-
+

{` / ${fund.name}`}

-
+
fund logo

- + Overview - {/* Positions + Positions Policies - Share Classes */} + Share Classes @@ -270,23 +275,23 @@ export default function ProductPage() {

Fees

-
+

Management Fee

{formatPercent(fund.fees.management)}
-
+

Performance Fee

{formatPercent(fund.fees.performance)}
-
+

Subscription Fee

{formatPercent(fund.fees.subscription)}
-
+

Redemption Fee

{formatPercent(fund.fees.redemption)}
@@ -294,7 +299,7 @@ export default function ProductPage() {
-
+
Redeem - + {}} /> - + {}} /> - +
- -
+ +

Facts

-
+

Share Class Asset

{fund.facts.fundAsset} @@ -373,9 +378,11 @@ export default function ProductPage() {
-
-

Terms

-
+ + +
+

Terms

+

High-Water Mark

diff --git a/web/src/app/products/products-overview.tsx b/web/src/app/products/products-overview.tsx index e3ef18bf..5fd30c0d 100644 --- a/web/src/app/products/products-overview.tsx +++ b/web/src/app/products/products-overview.tsx @@ -13,42 +13,37 @@ import { TableToolbarSearch } from "@carbon/react"; -import { formatDateFromTimestamp } from "../utils/format-number"; -import { useGlamProgram } from "../glam/glam-data-access"; +import { PublicKey } from "@solana/web3.js"; + import { useNavigate } from "react-router-dom"; -export default function ProductsOverview() { - const { accounts } = useGlamProgram(); - const navigate = useNavigate(); +import { + useGlamProgramAccount, + useFundPerfChartData +} from "../glam/glam-data-access"; +import { TextAlignCenter } from "@carbon/icons-react"; - let rows: any[] = []; - if (accounts.data) { - rows = accounts.data.map((account) => { - const fund = account.account; - const imageKey = - fund.shareClasses[0].toBase58() || "1111111111111111111111111111111111"; - return { - imageURL: `https://api.glam.systems/image/${imageKey}.png`, - id: account.publicKey.toBase58(), - name: fund.name, - symbol: fund.symbol, - share_classes_len: fund.shareClassesLen, - assets_len: fund.assetsLen, - fees_management: - fund.shareClassesMetadata[0].feeManagement / 1_000_000.0, - fees_performance: - fund.shareClassesMetadata[0].feePerformance / 1_000_000.0, - inception: Math.floor( - new Date(fund.shareClassesMetadata[0].launchDate).getTime() / 1000 - ), - status: fund.shareClassesMetadata[0].lifecycle.toUpperCase() - }; - }); - } +export default function ProductsOverview() { + const defaultFund = "AdXkDnJpFKqZeoUygLvm5dp2b5JGVPz3rEWfGCtB5Kc2"; + const rows = [ + { + id: defaultFund, + name: "Glam Investment Fund", + symbol: "GBS", + /*nav: 100.1, + aum: 13796.12,*/ + share_classes_len: 1, + assets_len: 3, + fees_management: 1.5, + fees_performance: 10, + inception: 1712189348, + status: "Active" + } + ]; const headers = [ { - key: "imageURL", + key: "", header: "" }, { @@ -93,6 +88,62 @@ export default function ProductsOverview() { } ]; + const navigate = useNavigate(); + + class FundModel { + key: PublicKey; + data: any; + + constructor(key: PublicKey, data: any) { + this.key = key; + this.data = data || {}; + } + + getImageUrl() { + const pubkey = + this.data?.shareClasses[0].toBase58() || + "1111111111111111111111111111111111"; + return `https://api.glam.systems/image/${pubkey}.png`; + } + + getManagementFee() { + return this.data?.shareClassesMetadata[0].feeManagement / 1_000_000.0; + } + getPerformanceFee() { + return this.data?.shareClassesMetadata[0].feePerformance / 1_000_000.0; + } + } + + let id = "AdXkDnJpFKqZeoUygLvm5dp2b5JGVPz3rEWfGCtB5Kc2"; + + let fundKey = new PublicKey(defaultFund); + try { + fundKey = new PublicKey(id || defaultFund); + } catch (_e) { + // pass + } + const fundId = fundKey.toString(); + + const { account } = useGlamProgramAccount({ fundKey }); + if (account.isLoading) { + return ""; //spinner + } + + const fundModel = new FundModel(fundKey, account.data); + + function formatNumber(value: number): string { + return new Intl.NumberFormat("en-US").format(value); + } + + function formatDateFromTimestamp(timestampStr: string): string { + const date = new Date(Number(timestampStr) * 1000); + return new Intl.DateTimeFormat("en-US", { + month: "short", + day: "numeric", + year: "numeric" + }).format(date); + } + return (
@@ -114,7 +165,7 @@ export default function ProductsOverview() { {headers.map((header) => ( - // @ts-expect-error FIXME: No overload matches this call. + // @ts-ignore {header.header} @@ -139,13 +190,15 @@ export default function ProductsOverview() { ); } else if (cell.info.header === "aum") { return ( - {cell.value} + + {formatNumber(cell.value)} + ); - } else if (cell.info.header === "imageURL") { + } else if (cell.info.header === "") { return ( Fund { - return value != 0 ? `${value.toFixed(3)}%` : "0"; + return value != 0 ? `${(value).toFixed(3)}%` : "0"; }; export const formatNumber = (value: number) => { - return value.toLocaleString("en-US", { - style: "currency", + return value.toLocaleString('en-US', { + style: 'currency', currency: "USD", currencyDisplay: "symbol", minimumFractionDigits: 0, - maximumFractionDigits: 2 + maximumFractionDigits: 2, }); }; - -export function formatDateFromTimestamp(timestampStr: string): string { - const date = new Date(Number(timestampStr) * 1000); - return new Intl.DateTimeFormat("en-US", { - month: "short", - day: "numeric", - year: "numeric" - }).format(date); -}