Skip to content

Commit

Permalink
feat: add home widgets (#994)
Browse files Browse the repository at this point in the history
* feat: add home widgets

- app of the day
- block height
- latest used apps
- node status
- onchain fees
- what's new in alby hub

* fix: seeded random function

* fix: link to app on latest apps widget

* chore: remove connected peers from node status widget

* chore: minor adjustments to home page widgets

* chore: move technical home page widgets into a nerds card
  • Loading branch information
rolznz authored Jan 17, 2025
1 parent ff9ef56 commit d6c196a
Show file tree
Hide file tree
Showing 7 changed files with 390 additions and 80 deletions.
56 changes: 56 additions & 0 deletions frontend/src/components/home/widgets/AppOfTheDayWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { ExternalLinkIcon } from "lucide-react";
import { Link } from "react-router-dom";
import { suggestedApps } from "src/components/SuggestedAppData";
import { Button } from "src/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "src/components/ui/card";

export function AppOfTheDayWidget() {
function seededRandom(seed: number) {
const x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}

const daysSinceEpoch = Math.floor(Date.now() / (1000 * 60 * 60 * 24));
const todayIndex = Math.floor(
seededRandom(daysSinceEpoch) * suggestedApps.length
);
const app = suggestedApps[todayIndex];

return (
<Card>
<CardHeader>
<CardTitle>App of the Day</CardTitle>
</CardHeader>
<CardContent>
<div className="flex gap-3 items-center">
<img
src={app.logo}
alt="logo"
className="inline rounded-lg w-12 h-12"
/>
<div className="flex-grow">
<CardTitle>{app.title}</CardTitle>
<CardDescription>{app.description}</CardDescription>
</div>
</div>
</CardContent>
<CardFooter className="flex flex-row justify-end">
<Link
to={app.internal ? `/internal-apps/${app.id}` : `/appstore/${app.id}`}
>
<Button variant="outline">
<ExternalLinkIcon className="w-4 h-4 mr-2" />
Open
</Button>
</Link>
</CardFooter>
</Card>
);
}
25 changes: 25 additions & 0 deletions frontend/src/components/home/widgets/BlockHeightWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "src/components/ui/card";
import { useMempoolApi } from "src/hooks/useMempoolApi";

export function BlockHeightWidget() {
const { data: blocks } = useMempoolApi<{ height: number }[]>("/v1/blocks");

if (!blocks?.length) {
return null;
}
return (
<Card>
<CardHeader>
<CardTitle>Block Height</CardTitle>
</CardHeader>
<CardContent className="mt-6">
<p className="text-4xl font-semibold">{blocks[0].height}</p>
</CardContent>
</Card>
);
}
55 changes: 55 additions & 0 deletions frontend/src/components/home/widgets/LatestUsedAppsWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import dayjs from "dayjs";
import { ChevronRight } from "lucide-react";
import { Link } from "react-router-dom";
import AppAvatar from "src/components/AppAvatar";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "src/components/ui/card";
import { useApps } from "src/hooks/useApps";

export function LatestUsedAppsWidget() {
const { data: apps } = useApps();
if (!apps?.length) {
return null;
}

return (
<Card>
<CardHeader>
<CardTitle>Latest Used Apps</CardTitle>
</CardHeader>
<CardContent className="grid grid-cols-1 gap-4">
{apps
.sort(
(a, b) =>
new Date(b.lastEventAt ?? 0).getTime() -
new Date(a.lastEventAt ?? 0).getTime()
)
.slice(0, 3)
.map((app) => (
<Link key={app.id} to={`/apps/${app.appPubkey}`}>
<div className="flex items-center justify-between w-full">
<div className="flex items-center justify-center gap-4">
<AppAvatar app={app} className="w-14 h-14 rounded-lg" />
<p className="text-xl font-semibold">
{app.name === "getalby.com" ? "Alby Account" : app.name}
</p>
</div>
<div className="flex items-center justify-center gap-4">
<p className="text-sm text-muted-foreground">
{app.lastEventAt
? dayjs(app.lastEventAt).fromNow()
: "never"}
</p>
<ChevronRight className="text-muted-foreground w-8 h-8" />
</div>
</div>
</Link>
))}
</CardContent>
</Card>
);
}
39 changes: 39 additions & 0 deletions frontend/src/components/home/widgets/NodeStatusWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Link } from "react-router-dom";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "src/components/ui/card";
import { useChannels } from "src/hooks/useChannels";
import { usePeers } from "src/hooks/usePeers";

export function NodeStatusWidget() {
const { data: channels } = useChannels();
const { data: peers } = usePeers();

if (!channels || !peers) {
return null;
}
return (
<Link to="/channels">
<Card>
<CardHeader>
<CardTitle>Node</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground text-xs">Channels Online</p>
<p className="text-xl font-semibold">
{channels.filter((c) => c.active).length || 0} /{" "}
{channels.filter((c) => c.active).length || 0}
</p>
<p className="text-muted-foreground text-xs mt-6">Connected Peers</p>
<p className="text-xl font-semibold">
{peers.filter((p) => p.isConnected).length || 0} /{" "}
{peers.filter((p) => p.isConnected).length || 0}
</p>
</CardContent>
</Card>
</Link>
);
}
55 changes: 55 additions & 0 deletions frontend/src/components/home/widgets/OnchainFeesWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "src/components/ui/card";
import { useMempoolApi } from "src/hooks/useMempoolApi";

export function OnchainFeesWidget() {
const { data: recommendedFees } = useMempoolApi<{
fastestFee: number;
halfHourFee: number;
economyFee: number;
minimumFee: number;
}>("/v1/fees/recommended");

if (!recommendedFees) {
return null;
}

const entries = [
{
title: "No priority",
value: recommendedFees.minimumFee,
},
{
title: "Low priority",
value: recommendedFees.economyFee,
},
{
title: "Medium priority",
value: recommendedFees.halfHourFee,
},
{
title: "High priority",
value: recommendedFees.fastestFee,
},
];

return (
<Card>
<CardHeader>
<CardTitle>On-Chain Fees</CardTitle>
</CardHeader>
<CardContent className="grid grid-cols-2 gap-6">
{entries.map((entry) => (
<div key={entry.title}>
<p className="text-muted-foreground text-xs">{entry.title}</p>
<p className="text-xl font-semibold">{entry.value} sat/vB</p>
</div>
))}
</CardContent>
</Card>
);
}
42 changes: 42 additions & 0 deletions frontend/src/components/home/widgets/WhatsNewWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { compare } from "compare-versions";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "src/components/ui/card";
import { useAlbyInfo } from "src/hooks/useAlbyInfo";
import { useInfo } from "src/hooks/useInfo";

export function WhatsNewWidget() {
const { data: info } = useInfo();
const { data: albyInfo } = useAlbyInfo();

if (!info || !albyInfo || !albyInfo.hub.latestReleaseNotes) {
return null;
}

const upToDate =
info.version &&
info.version.startsWith("v") &&
compare(info.version.substring(1), albyInfo.hub.latestVersion, ">=");

return (
<Card>
<CardHeader>
<CardTitle>What's New in {upToDate && "your "}Alby Hub?</CardTitle>
</CardHeader>
<CardContent>
<p className="text-xl font-semibold">v{albyInfo.hub.latestVersion}</p>
<p className="text-muted-foreground mt-1">
{albyInfo.hub.latestReleaseNotes}
</p>
{!upToDate && (
<p className="font-semibold mt-2 text-sm">
Make sure to update! you're currently running {info.version}
</p>
)}
</CardContent>
</Card>
);
}
Loading

0 comments on commit d6c196a

Please sign in to comment.