Skip to content

Commit

Permalink
feat: QA changes
Browse files Browse the repository at this point in the history
  • Loading branch information
borcherd committed Oct 1, 2024
1 parent ad88115 commit b90bb8a
Show file tree
Hide file tree
Showing 27 changed files with 485 additions and 350 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"clsx": "^2.1.1",
"lucide-react": "^0.439.0",
"next": "14.2.8",
"next-themes": "^0.3.0",
"react": "^18",
"react-dom": "^18",
"tailwind-merge": "^2.5.2",
Expand Down
11 changes: 3 additions & 8 deletions src/app/api/getCsvData/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ import { NextResponse } from "next/server";

export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const tokenSymbol = searchParams.get("tokenSymbol");
const tokenAddress = searchParams.get("tokenAddress");
const type = searchParams.get("type");

if (!tokenSymbol || !type) {
if (!tokenAddress || !type) {
return NextResponse.json(
{ error: "Missing tokenSymbol or type query parameters" },
{ status: 400 }
);
}

try {
const fileUrl = `https://storage.googleapis.com/mrgn-public/risk-model-output/${tokenSymbol}-${type}-model-output.csv`;
const fileUrl = `https://storage.googleapis.com/mrgn-public/risk/${tokenAddress}-${type}-model_output.csv`;
const response = await fetch(fileUrl);

if (response.ok) {
Expand All @@ -30,19 +30,14 @@ export async function GET(request: Request) {
}
);
} else if (response.status === 404) {
console.error(`File not found on Google Cloud Storage: ${fileUrl}`);
return NextResponse.json({ error: "File not found" }, { status: 404 });
} else {
console.error(
`Failed to fetch file from Google Cloud Storage: ${fileUrl}`
);
return NextResponse.json(
{ error: "Failed to fetch file from Google Cloud Storage" },
{ status: response.status }
);
}
} catch (error) {
console.error("Error fetching CSV file:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
Expand Down
12 changes: 10 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import "../styles/globals.css";
import { Layout } from "~/components/layout";
import { ThemeProvider } from "~/components/theme-provider";

export const metadata: Metadata = {
title: "Marginfi risk dashboard",
Expand All @@ -13,9 +14,16 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<html lang="en" suppressHydrationWarning>
<body>
<Layout>{children}</Layout>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<Layout>{children}</Layout>
</ThemeProvider>
</body>
</html>
);
Expand Down
52 changes: 39 additions & 13 deletions src/components/common/global-components/filter-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,58 @@ import * as React from "react";
interface FilterComponentProps {
selectedFilter: string;
setSelectedFilter: React.Dispatch<React.SetStateAction<string>>;
items: string[];
items: string[] | string[][];
}

export const FilterComponent = ({
selectedFilter,
setSelectedFilter,
items,
}: FilterComponentProps) => {
const isMultiArray = Array.isArray(items[0]);

return (
<dropdown.DropdownMenu>
<dropdown.DropdownMenuTrigger asChild>
<button className="flex outline-none items-center gap-1 justify-center w-max px-2 py-1 rounded-md border ">
<button className="flex outline-none items-center gap-1 justify-center w-max px-2 py-1 rounded-md border border-border bg-foreground text-background hover:bg-gray-200 dark:bg-background dark:text-foreground dark:hover:bg-muted transition-colors">
<MenuIcon size={18} />
{selectedFilter}
</button>
</dropdown.DropdownMenuTrigger>
<dropdown.DropdownMenuContent>
{items.map((item, index) => (
<dropdown.DropdownMenuItem
onClick={() => {
setSelectedFilter(item);
}}
key={index}
>
{item}
</dropdown.DropdownMenuItem>
))}
<dropdown.DropdownMenuContent
align="end"
className=" bg-foreground border border-border text-background dark:bg-background dark:text-foreground transition-colors"
>
{isMultiArray
? (items as string[][]).map((subArray, arrayIndex) => (
<React.Fragment key={arrayIndex}>
{subArray.map((item, index) => (
<dropdown.DropdownMenuItem
onClick={() => {
setSelectedFilter(item);
}}
key={`${arrayIndex}-${index}`}
className="cursor-pointer px-4 py-2 hover:bg-gray-200 dark:hover:bg-muted transition-colors"
>
{item}
</dropdown.DropdownMenuItem>
))}
{arrayIndex < (items as string[][]).length - 1 && (
<dropdown.DropdownMenuSeparator className="h-px bg-border my-1" />
)}
</React.Fragment>
))
: (items as string[]).map((item, index) => (
<dropdown.DropdownMenuItem
onClick={() => {
setSelectedFilter(item);
}}
key={index}
className="cursor-pointer px-4 py-2 hover:bg-gray-200 dark:hover:bg-muted transition-colors"
>
{item}
</dropdown.DropdownMenuItem>
))}
</dropdown.DropdownMenuContent>
</dropdown.DropdownMenu>
);
Expand Down
1 change: 1 addition & 0 deletions src/components/common/global-components/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./search-component";
export * from "./filter-component";
export * from "./theme-toggle";
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const SearchComponent = ({
placeholder={placeHolder}
value={value}
onChange={(e) => setValue(e.target.value)}
className={`sm:max-w-sm max-w-56 ${className}`}
className={`sm:max-w-sm max-w-56 border border-border bg-foreground text-background placeholder-muted-foreground dark:bg-background dark:text-foreground dark:placeholder-muted-foreground transition-colors focus:ring-2 focus:ring-primary focus:border-primary ${className}`}
/>
);
};
56 changes: 56 additions & 0 deletions src/components/common/global-components/theme-toggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"use client";

import * as React from "react";
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";

import { Button } from "~/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "~/components/ui/dropdown";

export function ThemeToggle() {
const { theme, setTheme, resolvedTheme } = useTheme();
const [mounted, setMounted] = React.useState(false);

React.useEffect(() => {
setMounted(true);
}, []);
if (!mounted) return null;

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="icon"
className="bg-foreground text-background border-border hover:bg-gray-200 dark:bg-background dark:text-foreground dark:border-border dark:hover:bg-gray-700 transition-colors"
>
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0 text-background dark:text-foreground" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100 text-background dark:text-foreground" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="bg-foreground text-background border border-border dark:bg-background dark:text-foreground dark:border-border transition-colors"
align="end"
>
<DropdownMenuItem
onClick={() => setTheme("light")}
className="cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
>
Light
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => setTheme("dark")}
className="cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
>
Dark
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
4 changes: 3 additions & 1 deletion src/components/common/oracles/components/clock-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@ export const LiveClock: React.FC = () => {
return () => clearInterval(intervalId);
}, []);

return <div className="text-2xl">{currentTime}</div>;
return (
<div className="text-2xl dark:text-white text-black">{currentTime}</div>
);
};
92 changes: 50 additions & 42 deletions src/components/common/oracles/components/grid-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ import {
} from "~/components/ui/tooltip";
import React from "react";
import { oracleData } from "~/types";
import { OraclePrice } from "@mrgnlabs/marginfi-client-v2";
import { IconExclamationCircle } from "~/components/ui/icons";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "~/components/ui/card";

interface GridComponentProps {
data: oracleData[];
Expand All @@ -23,11 +29,49 @@ export const GridComponent = ({ data }: GridComponentProps) => {
? data.map((row, index) => (
<Card
key={index}
iconURI={row.tokenImage}
symbol={row.tokenSymbol!}
oraclePrice={row.oraclePrice!}
isStale={row.isStale}
/>
className="bg-foreground text-background gap-4 dark:bg-background dark:text-foreground transition-colors flex w-full flex-col items-center sm:w-52 h-52 p-4"
>
<CardHeader className="flex flex-col gap-2 p-2 justify-center items-center">
<CardTitle className="overflow-hidden text-ellipsis whitespace-nowrap text-background dark:text-foreground">
{row.tokenSymbol}
</CardTitle>
<CardDescription>
<Image
src={row.tokenImage}
alt={""}
width={48}
height={48}
/>
</CardDescription>
</CardHeader>
<CardContent>
<span>
{tokenPriceFormatter(
row.oraclePrice?.priceRealtime?.price?.toNumber() ?? 0
)}
</span>
{row.isStale && (
<div className="flex items-center gap-1">
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<IconExclamationCircle
className="text-red-600"
size={24}
/>
</TooltipTrigger>
<TooltipContent>
<p>{`Last updated: ${new Date(
row.oraclePrice?.timestamp.toNumber() ?? 0
)?.toTimeString()}`}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<span className="text-red-600">Stale</span>
</div>
)}
</CardContent>
</Card>
))
: Array.from({ length: 3 }).map((_, index) => (
<div
Expand All @@ -39,39 +83,3 @@ export const GridComponent = ({ data }: GridComponentProps) => {
</div>
);
};

const Card = (info: {
symbol: string;
iconURI: string;
oraclePrice: OraclePrice;
isStale: boolean;
}) => {
return (
<div className="flex gap-2 w-full flex-col items-center justify-center sm:w-52 h-52 p-4 bg-black border-background-gray-light/50 rounded-md">
<Image src={info.iconURI} alt={""} width={48} height={48} />{" "}
<h4 className="text-2xl">{info.symbol}</h4>
<span>
{tokenPriceFormatter(
info.oraclePrice?.priceRealtime?.price?.toNumber()
)}
</span>
{info.isStale && (
<div className="flex items-center gap-1">
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<IconExclamationCircle className="text-red-600" size={24} />
</TooltipTrigger>
<TooltipContent>
<p>{`Last updated: ${new Date(
info.oraclePrice.timestamp.toNumber()
)?.toTimeString()}`}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<span className="text-red-600">Stale</span>
</div>
)}
</div>
);
};
29 changes: 20 additions & 9 deletions src/components/common/oracles/oracles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,25 @@ export const Oracles = () => {
};

React.useEffect(() => {
if (!mrgnClient) {
const connection = new Connection( // eslint-disable-next-line
process.env.NEXT_PUBLIC_MARGINFI_RPC_ENDPOINT!,
"confirmed"
);
fetchMrgnClient({ connection });
}
const connection = new Connection( // eslint-disable-next-line
process.env.NEXT_PUBLIC_MARGINFI_RPC_ENDPOINT!,
"confirmed"
);
fetchMrgnClient({ connection });

const interval = setInterval(() => {
refetchMrgnClient({ connection });
}, 30000);

return () => clearInterval(interval);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

React.useEffect(() => {
if (mrgnClient) fetchData(mrgnClient);
if (mrgnClient) {
fetchData(mrgnClient);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [mrgnClient]);

React.useEffect(() => {
Expand All @@ -98,7 +105,11 @@ export const Oracles = () => {
return (
<div className="w-full p-3 sm:p-6 flex flex-col gap-4">
<div className="flex justify-between items-center gap-2">
<Button className="w-20" onClick={handleRefresh} disabled={isLoading}>
<Button
className="w-20 dark:bg-white border-background bg-black text-white dark:text-black transition-colors hover:bg-black/90 hover:text-white"
onClick={handleRefresh}
disabled={isLoading}
>
{isLoading ? <IconLoader /> : "Refresh"}
</Button>
<staleOracleComponents.LiveClock />
Expand Down
Loading

0 comments on commit b90bb8a

Please sign in to comment.